ChatGPT解决这个技术问题 Extra ChatGPT

Private properties in JavaScript ES6 classes

Is it possible to create private properties in ES6 classes?

Here's an example. How can I prevent access to instance.property?

class Something {
  constructor(){
    this.property = "test";
  }
}

var instance = new Something();
console.log(instance.property); //=> "test"
There is actually stage 3 proposal for this feature - tc39.github.io/proposal-class-fields github.com/tc39/proposal-class-fields
@arty I've provided an answer to this with examples: stackoverflow.com/a/52237988/1432509
All solutions for private properties / methods, ES5 / ES6+, all fail to provide real privacy since Chrome based browsers can always reveal the entire execution context in the [Scopes] object of any prototype. Some things just have to be coded outside the browser scope. In my test, no method will hide anything from Chrome.

k
kmiklas

Short answer, no, there is no native support for private properties with ES6 classes.

But you could mimic that behaviour by not attaching the new properties to the object, but keeping them inside a class constructor, and use getters and setters to reach the hidden properties. Note that the getters and setters gets redefine on each new instance of the class.

ES6

class Person {
    constructor(name) {
        var _name = name
        this.setName = function(name) { _name = name; }
        this.getName = function() { return _name; }
    }
}

ES5

function Person(name) {
    var _name = name
    this.setName = function(name) { _name = name; }
    this.getName = function() { return _name; }
}

I like this solution the best. I agree it shouldn't be used for scaling but it's perfect for classes that will usually only be instantiated once per include.
Also you are redefining every single component of this class each time a new is created.
This is so weird! In ES6 you are creating more "closure pyramids" than before ES6! Defining functions WITHIN a constructor looks uglier than it did in the above ES5 example.
All this does is introduce indirection. Now how do you make the getName and setName properties private?
@aij So name one language that does not do the same. You can easily see that he could just comment out the setter or the getter or both and that _name is truly private.
T
Tobi Akanji

Private class features is in Stage 3 proposal. The majority of its features are supported by all major browsers.

class Something {
  #property;

  constructor(){
    this.#property = "test";
  }

  #privateMethod() {
    return 'hello world';
  }

  getPrivateMessage() {
      return this.#property;
  }
}

const instance = new Something();
console.log(instance.property); //=> undefined
console.log(instance.privateMethod); //=> undefined
console.log(instance.getPrivateMessage()); //=> test
console.log(instance.#property); //=> Syntax error

And what about eslint? I got parser error at equal sign. Babel is working, just eslint can't parse this new js syntax.
Wow this is very ugly. Hashtag is a valid character. The property is not really private, or? .. I checked it in TypeScript. Private members are not compiled in private or read-only (from outside). Just declared like another (public) property. (ES5).
By now, this solution is also officially supported in Google Chrome and Node.js v12. Private getters and setters are in development.
How do you write private methods with this? Can I do this: #beep() {}; and this: async #bzzzt() {}?
Using _ would be a breaking change, unless what you mean is that JS doesn't need private private properties at all
t
twhb

Yes, prefix the name with # and include it in the class definition, not just the constructor.

Real private properties were finally added in ES2022. As of 2022-03-22, private properties (fields and methods) have been supported in all major browsers for at least six months, but 10-20% of users are still on older browsers [Can I Use].

Example:

class Person {
  #age

  constructor(name) {
    this.name = name; // this is public
    this.#age = 20; // this is private
  }

  greet() {
    // here we can access both name and age
    console.log(`name: ${this.name}, age: ${this.#age}`);
  }
}

let joe = new Person('Joe');
joe.greet();

// here we can access name but not age

Following are methods for keeping properties private in pre-ES2022 environments, with various tradeoffs.

Scoped variables

The approach here is to use the scope of the constructor function, which is private, to store private data. For methods to have access to this private data they must be created within the constructor as well, meaning you're recreating them with every instance. This is a performance and memory penalty, but it may be acceptable. The penalty can be avoided for methods that do not need access to private data by declaring them in the normal way.

Example:

class Person {
  constructor(name) {
    let age = 20; // this is private
    this.name = name; // this is public

    this.greet = () => {
      // here we can access both name and age
      console.log(`name: ${this.name}, age: ${age}`);
    };
  }

  anotherMethod() {
    // here we can access name but not age
  }
}

let joe = new Person('Joe');
joe.greet();

// here we can access name but not age

Scoped WeakMap

A WeakMap can be used to improve the performance of the above approach, in exchange for even more clutter. WeakMaps associate data with Objects (here, class instances) in such a way that it can only be accessed using that WeakMap. So, we use the scoped variables method to create a private WeakMap, then use that WeakMap to retrieve private data associated with this. This is faster than the scoped variables method because all your instances can share a single WeakMap, so you don't need to recreate methods just to make them access their own WeakMaps.

Example:

let Person = (function () {
  let privateProps = new WeakMap();

  return class Person {
    constructor(name) {
      this.name = name; // this is public
      privateProps.set(this, {age: 20}); // this is private
    }

    greet() {
      // Here we can access both name and age
      console.log(`name: ${this.name}, age: ${privateProps.get(this).age}`);
    }
  };
})();

let joe = new Person('Joe');
joe.greet();

// here we can access name but not age

This example uses a WeakMap with Object keys to use one WeakMap for multiple private properties; you could also use multiple WeakMaps and use them like privateAge.set(this, 20), or write a small wrapper and use it another way, like privateProps.set(this, 'age', 0).

The privacy of this approach could theoretically be breached by tampering with the global WeakMap object. That said, all JavaScript can be broken by mangled globals.

(This method could also be done with Map, but WeakMap is better because Map will create memory leaks unless you're very careful, and for this purpose the two aren't otherwise different.)

Half-Answer: Scoped Symbols

A Symbol is a type of primitive value that can serve as a property name instead of a string. You can use the scoped variable method to create a private Symbol, then store private data at this[mySymbol].

The privacy of this method can be breached using Object.getOwnPropertySymbols, but is somewhat awkward to do.

Example:

let Person = (() => {
  let ageKey = Symbol();

  return class Person {
    constructor(name) {
      this.name = name; // this is public
      this[ageKey] = 20; // this is intended to be private
    }

    greet() {
      // Here we can access both name and age
      console.log(`name: ${this.name}, age: ${this[ageKey]}`);
    }
  }
})();

let joe = new Person('Joe');
joe.greet();

// Here we can access joe's name and, with a little effort, age. We can’t
// access ageKey directly, but we can obtain it by listing all Symbol
// properties on `joe` with `Object.getOwnPropertySymbols(joe)`.

Note that making a property non-enumerable using Object.defineProperty does not prevent it from being included in Object.getOwnPropertySymbols.

Half-Answer: Underscores

The old convention is to just use a public property with an underscore prefix. This does not keep it private, but it does do a good job of communicating to readers that they should treat it as private, which often gets the job done. In exchange for this, we get an approach that's easier to read, easier to type, and faster than the other workarounds.

Example:

class Person {
  constructor(name) {
    this.name = name; // this is public
    this._age = 20; // this is intended to be private
  }

  greet() {
    // Here we can access both name and age
    console.log(`name: ${this.name}, age: ${this._age}`);
  }
}

let joe = new Person('Joe');
joe.greet();

// Here we can access both joe's name and age. But we know we aren't
// supposed to access his age, which just might stop us.

Summary

ES2022: great but not yet supported by all visitors

Scoped variables: private, slower, awkward

Scoped WeakMaps: hackable, awkward

Scoped Symbols: enumerable and hackable, somewhat awkward

Underscores: just a request for privacy, no other downsides


The first example snippet ("scoped variables") is a total antipattern - each returned object will have a different class. Don't do that. If you want privileged methods, create them in the constructor.
Wrapping a class inside a function seems to defeat the whole purpose of using classes in the first place. If you already use the function to create an instance, you might as well place all your private/public members inside that function as well, and forget about the whole class keyword.
@Bergi @Kokodoko I edited the scoped variables approach to be slightly faster and not break instanceof. I admit I was thinking of that approach as included only for completeness' sake and should have given more thought to how much it is actually capable of.
Excellent explanation! I'm still surprised that ES6 actually made it harder to simulate a private variable, where in ES5 you could just use var and this inside a function to simulate private and public.
@Kokodoko If you dispense with the class and just put everything in the function, you will also have to revert to implementing inheritance using the prototype method. Using extend on classes is by far a cleaner approach, so using a class inside a function is totally acceptable.
M
Mahmoud

Update: A proposal with nicer syntax is on its way. Contributions are welcome.

Yes, there is - for scoped access in objects - ES6 introduces Symbols.

Symbols are unique, you can't gain access to one from the outside except with reflection (like privates in Java/C#) but anyone who has access to a symbol on the inside can use it for key access:

var property = Symbol();
class Something {
    constructor(){
        this[property] = "test";
    }
}

var instance = new Something();

console.log(instance.property); //=> undefined, can only access with access to the Symbol

Can't you use Object.getOwnPropertySymbols? ;)
@BenjaminGruenbaum: Apparently Symbols no longer ensure true privacy: stackoverflow.com/a/22280202/1282216
@trusktr through thre keys? No. Through the symbols? Yes. Very much like how you can use reflection in languages like C# and Java to access private fields. Access modifiers aren't about security - they're about clarity of intent.
It seems like using Symbols is similar to doing const myPrivateMethod = Math.random(); Something.prototype[''+myPrivateMethod] = function () { ... } new Something()[''+myPrivateMethod]();.This isn't really privacy, it's obscurity, in the sense of traditional JavaScript. I would consider "private" JavaScript to mean using closures to encapsulate variables. Those variables are hence not accessible through reflection.
Also, I feel that using the private and protected keywords would be so much cleaner than Symbol or Name. I prefer dot notation rather than bracket notation. I'd like to keep using a dot for private things. this.privateVar
C
Community

The answer is "No". But you can create private access to properties like this:

Use modules. Everything in a module is private unless it's made public by using the export keyword.

Inside modules, use function closure: http://www.kirupa.com/html5/closures_in_javascript.htm

(The suggestion that Symbols could be used to ensure privacy was true in an earlier version of the ES6 spec but is no longer the case:https://mail.mozilla.org/pipermail/es-discuss/2014-January/035604.html and https://stackoverflow.com/a/22280202/1282216. For a longer discussion about Symbols and privacy see: https://curiosity-driven.org/private-properties-in-javascript)


-1, this does not answer your question really. (You can use closures with IIFEs in ES5 too). Private properties are enumerable through reflection in most languages (Java, C#, etc). The point of private properties is to convey intent to other progrmamers and not to enforce security.
@BenjaminGruenbaum, I know, I wish I had a better answer, I'm not happy with it either.
I think symbols are still a valid way to achieve inaccessible members while in the programming environment. Yes, they can still be found if you reallllyyy want, but that's not the point is it? You shouldn't store sensitive information in it, but you shouldn't do that anyway in client-side code. But it works for the purpose of hiding a property or method from an outside class.
Using variables scoped at the level of a module as substitute for private properties in a class will lead to a singleton.behavior or behavior similart to statitc properties.Instances of vars will get shared.
l
loganfsmyth

The only way to get true privacy in JS is through scoping, so there is no way to have a property that is a member of this that will be accessible only inside the component. The best way to store truly private data in ES6 is with a WeakMap.

const privateProp1 = new WeakMap();
const privateProp2 = new WeakMap();

class SomeClass {
  constructor() {
    privateProp1.set(this, "I am Private1");
    privateProp2.set(this, "I am Private2");

    this.publicVar = "I am public";
    this.publicMethod = () => {
      console.log(privateProp1.get(this), privateProp2.get(this))
    };        
  }

  printPrivate() {
    console.log(privateProp1.get(this));
  }
}

Obviously this is a probably slow, and definitely ugly, but it does provide privacy.

Keep in mind that EVEN THIS isn't perfect, because Javascript is so dynamic. Someone could still do

var oldSet = WeakMap.prototype.set;
WeakMap.prototype.set = function(key, value){
    // Store 'this', 'key', and 'value'
    return oldSet.call(this, key, value);
};

to catch values as they are stored, so if you wanted to be extra careful, you'd need to capture a local reference to .set and .get to use explicitly instead of relying on the overridable prototype.

const {set: WMSet, get: WMGet} = WeakMap.prototype;

const privateProp1 = new WeakMap();
const privateProp2 = new WeakMap();

class SomeClass {
  constructor() {
    WMSet.call(privateProp1, this, "I am Private1");
    WMSet.call(privateProp2, this, "I am Private2");

    this.publicVar = "I am public";
    this.publicMethod = () => {
      console.log(WMGet.call(privateProp1, this), WMGet.call(privateProp2, this))
    };        
  }

  printPrivate() {
    console.log(WMGet.call(privateProp1, this));
  }
}

As a suggestion, you can avoid using one weak map per property by using an object as value. This way you can also reduce the number of map's get to one per method (e.g. const _ = privates.get(this); console.log(_.privateProp1);).
Yup, that's totally an option too. I mostly went with this since it maps more directly to what a user would have written when using real properties.
@loganfsmyth const myObj = new SomeClass(); console.log(privateProp1.get(myObj)) // "I am Private1" that means that your property is private or not?
For that to work, the code accessing the property would need access to the WeakMap object, which would normally be scoped inside of a module and inaccessible
C
Community

For future reference of other on lookers, I'm hearing now that the recommendation is to use WeakMaps to hold private data.

Here is a more clear, working example:

function storePrivateProperties(a, b, c, d) {
  let privateData = new WeakMap;
  // unique object as key, weak map can only accept object as key, when key is no longer referened, garbage collector claims the key-value 
  let keyA = {}, keyB = {}, keyC = {}, keyD = {};

  privateData.set(keyA, a);
  privateData.set(keyB, b);
  privateData.set(keyC, c);
  privateData.set(keyD, d);

  return {
    logPrivateKey(key) {
      switch(key) {
      case "a":
        console.log(privateData.get(keyA));
        break;
      case "b":
        console.log(privateData.get(keyB));
        break;
      case "c":
        console.log(privateData.get(keyC));
        break;
      case "d":
        console.log(privateData.set(keyD));
        break;
      default:
        console.log(`There is no value for ${key}`)
      }
    }
  }
}

Be aware that these properties are static.
I didn't downvote you but your weakmap example is completely wrong.
Namely - You're sharing the data between all class instances and not per instance - may I at least fix it?
Indeed, the weakmap needs to be attached to a given instance. See fitzgeraldnick.com/weblog/53 for an example.
According to MDN, primitive data types such as Symbols are not allowed as a WeakMap key. MDN WeakMap Documentation
B
Bergi

Depends on whom you ask :-)

No private property modifier is included in the Maximally minimal classes proposal which seems to have made it into the current draft.

However, there might be support for private names, which does allow private properties - and they probably could be used in class definitions as well.


It's highly unlikely that private names will make it into ES6, though they're thinking of some form of private thing for ES7.
@Qantas94Heavy both private names and unique string values have been superseded by Symbols from what I understand.
Yeah, it probably will become Symbols. However, afaik the "symbols" currently contained in the spec are only used to describe internal properties like [[prototype]], and there is no way to create and use them in user code. Do you know some docs?
I just realized that modules can be used to set privacy. Combined with Symbols that might be all you'd ever need ...?
@Cody: Your whole module code does have its own scope in ES6 anyway, no need for an IEFE. And yes, symbols are purposed for uniqueness (collision-avoidance), not privacy.
J
Johnny Oshika

Using ES6 modules (initially proposed by @d13) works well for me. It doesn't mimic private properties perfectly, but at least you can be confident that properties that should be private won't leak outside of your class. Here's an example:

something.js

let _message = null;
const _greet = name => {
  console.log('Hello ' + name);
};

export default class Something {
  constructor(message) {
    _message = message;
  }

  say() {
    console.log(_message);
    _greet('Bob');
  }
};

Then the consuming code can look like this:

import Something from './something.js';

const something = new Something('Sunny day!');
something.say();
something._message; // undefined
something._greet(); // exception

Update (Important):

As @DanyalAytekin outlined in the comments, these private properties are static, so therefore global in scope. They will work well when working with Singletons, but care must be taken for Transient objects. Extending the example above:

import Something from './something.js';
import Something2 from './something.js';

const a = new Something('a');
a.say(); // a

const b = new Something('b');
b.say(); // b

const c = new Something2('c');
c.say(); // c

a.say(); // c
b.say(); // c
c.say(); // c

Good for private static.
@DanyalAytekin: that's a very good point. These private properties are static so global in scope. I've updated my answer to reflect this.
The more I learn about functional programming (especially Elm and Haskell) the more I believe that JS programmers would benefit from a module-based approach to "modularity" rather than an OOP class-based one. If we think of ES6 modules as the foundations for building applications, and forget about classes entirely, I believe we may end up with much better applications overall. Could any experienced Elm or Haskell users comment on this approach?
In the update, the second a.say(); // a should be b.say(); // b
tried let _message = null way, not so cool, when call constructor multiple times, it mess up.
N
Nikita Kurtin

Yes - you can create encapsulated property, but it's not been done with access modifiers (public|private) at least not with ES6.

Here is a simple example how it can be done with ES6:

1 Create class using class word

2 Inside it's constructor declare block-scoped variable using let OR const reserved words -> since they are block-scope they cannot be accessed from outside (encapsulated)

3 To allow some access control (setters|getters) to those variables you can declare instance method inside it's constructor using: this.methodName=function(){} syntax

"use strict";
    class Something{
        constructor(){
            //private property
            let property="test";
            //private final (immutable) property
            const property2="test2";
            //public getter
            this.getProperty2=function(){
                return property2;
            }
            //public getter
            this.getProperty=function(){
                return property;
            }
            //public setter
            this.setProperty=function(prop){
                property=prop;
            }
        }
    }

Now lets check it:

var s=new Something();
    console.log(typeof s.property);//undefined 
    s.setProperty("another");//set to encapsulated `property`
    console.log(s.getProperty());//get encapsulated `property` value
    console.log(s.getProperty2());//get encapsulated immutable `property2` value

This is (for now) the only one solution to this problem despite the fact that all methods declared in the constructor are redeclared for each instance of the class. This is pretty a bad idea regarding performance and memory usage. Class methods should be declared outside of the constructor scope.
@Freezystem First: First those are instance methods (not Class methods). Second OP question was: _ How can I prevent access to instance.property?_ and my answer is: an example of how... Third if you have any better idea - let's hear it
I was not saying you were wrong, I said that your solution was the best compromise to achieve private variable despite the fact that a copy of each instance methods is created each time you call new Something(); because your methods are declared in the constructor to have access to these private variables. That may cause a lot of memory consumption if you create a lot of instance of your class, so performance issues. Methods should have been declared outside of constructor scope. My comment was more an explanation of your solution drawbacks than a criticism.
But isn't it bad practice do define your entire class inside the constructor? Aren't we just "hacking" javascript now? Just look at any other OOP programming language, and you'll see that the constructor is not meant to define a class.
Yes that's what I meant, and your solution works! I'm just saying that in general I am surprised that ES6 added a 'class' keyword, but removed the elegant solution of working with var and this, to achieve encapsulation.
e
efidiles

Completing @d13 and the comments by @johnny-oshika and @DanyalAytekin:

I guess in the example provided by @johnny-oshika we could use normal functions instead of arrow functions and then .bind them with the current object plus a _privates object as a curried parameter:

something.js

function _greet(_privates) {
  return 'Hello ' + _privates.message;
}

function _updateMessage(_privates, newMessage) {
  _privates.message = newMessage;
}

export default class Something {
  constructor(message) {
    const _privates = {
      message
    };

    this.say = _greet.bind(this, _privates);
    this.updateMessage = _updateMessage.bind(this, _privates);
  }
}

main.js

import Something from './something.js';

const something = new Something('Sunny day!');

const message1 = something.say();
something.updateMessage('Cloudy day!');
const message2 = something.say();

console.log(message1 === 'Hello Sunny day!');  // true
console.log(message2 === 'Hello Cloudy day!');  // true

// the followings are not public
console.log(something._greet === undefined);  // true
console.log(something._privates === undefined);  // true
console.log(something._updateMessage === undefined);  // true

// another instance which doesn't share the _privates
const something2 = new Something('another Sunny day!');

const message3 = something2.say();

console.log(message3 === 'Hello another Sunny day!'); // true

Benefits I can think of:

we can have private methods (_greet and _updateMessage act like private methods as long as we don't export the references)

although they're not on the prototype, the above mentioned methods will save memory because the instances are created once, outside the class (as opposed to defining them in the constructor)

we don't leak any globals since we're inside a module

we can also have private properties using the binded _privates object

Some drawbacks I can think of:

less intuitive

mixed usage of class syntax and old school patterns (object bindings, module/function scoped variables)

hard bindings - we can't rebind the public methods (although we can improve this by using soft bindings (https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch2.md#softening-binding))

A running snippet can be found here: http://www.webpackbin.com/NJgI5J8lZ


L
Lucio Paiva

A different approach to "private"

Instead of fighting against the fact that private visibility is currently unavailable in ES6, I decided to take a more practical approach that does just fine if your IDE supports JSDoc (e.g., Webstorm). The idea is to use the @private tag. As far as development goes, the IDE will prevent you from accessing any private member from outside its class. Works pretty well for me and it's been really useful for hiding internal methods so the auto-complete feature shows me just what the class really meant to expose. Here's an example:

https://i.stack.imgur.com/F6jXB.png


The problem is, that we don't wan't to access the private variables over the Editor, we wan't to protect the private variables from outside - And that is, what public/private does. If your code is finished, you can access (and the important thinks: override) these variables from outside the class. Your @private comment can not prevent these, it's only a Feature for documentation-generation and you'r IDE.
Yes, I am aware of that. It's just that that's enough for me and may be enough for other people out there. I know it's not really making my variables private; it's only warning me not to try and access it from outside (only, of course, if my team and I are all using an IDE that supports this feature). Javascript (and other languages, like Python) was not designed with access levels in mind. People do all sorts of stuff to somehow implement that functionality, but in the end we end up just hacking the language to achieve that. I decided to go with a more "natural" approach, if you will.
S
Sergei

Oh, so many exotic solutions! I usually don't care about privacy so I use "pseudo privacy" as it's said here. But if do care (if there are some special requirements for that) I use something like in this example:

class jobImpl{
  // public
  constructor(name){
    this.name = name;
  }
  // public
  do(time){
    console.log(`${this.name} started at ${time}`);
    this.prepare();
    this.execute();
  }
  //public
  stop(time){
    this.finish();
    console.log(`${this.name} finished at ${time}`);
  }
  // private
  prepare(){ console.log('prepare..'); }
  // private
  execute(){ console.log('execute..'); }
  // private
  finish(){ console.log('finish..'); }
}

function Job(name){
  var impl = new jobImpl(name);
  return {
    do: time => impl.do(time),
    stop: time => impl.stop(time)
  };
}

// Test:
// create class "Job"
var j = new Job("Digging a ditch");
// call public members..
j.do("08:00am");
j.stop("06:00pm");

// try to call private members or fields..
console.log(j.name); // undefined
j.execute(); // error

Another possible implementation of function (constructor) Job:

function Job(name){
  var impl = new jobImpl(name);
  this.do = time => impl.do(time),
  this.stop = time => impl.stop(time)
}

k
kevlened

WeakMap

supported in IE11 (Symbols are not)

hard-private (props using Symbols are soft-private due to Object.getOwnPropertySymbols)

can look really clean (unlike closures which require all props and methods in the constructor)

First, define a function to wrap WeakMap:

function Private() {
  const map = new WeakMap();
  return obj => {
    let props = map.get(obj);
    if (!props) {
      props = {};
      map.set(obj, props);
    }
    return props;
  };
}

Then, construct a reference outside your class:

const p = new Private();

class Person {
  constructor(name, age) {
    this.name = name;
    p(this).age = age; // it's easy to set a private variable
  }

  getAge() {
    return p(this).age; // and get a private variable
  }
}

Note: class isn't supported by IE11, but it looks cleaner in the example.


M
MarkM

I came across this post when looking for the best practice for "private data for classes". It was mentioned that a few of the patterns would have performance issues.

I put together a few jsperf tests based on the 4 main patterns from the online book "Exploring ES6":

http://exploringjs.com/es6/ch_classes.html#sec_private-data-for-classes

The tests can be found here:

https://jsperf.com/private-data-for-classes

In Chrome 63.0.3239 / Mac OS X 10.11.6, the best performing patterns were "Private data via constructor environments" and "Private data via a naming convention". For me Safari performed well for WeakMap but Chrome not so well.

I don't know the memory impact, but the pattern for "constructor environments" which some had warned would be a performance issue was very performant.

The 4 basic patterns are:

Private data via constructor environments

class Countdown {
    constructor(counter, action) {
        Object.assign(this, {
            dec() {
                if (counter < 1) return;
                counter--;
                if (counter === 0) {
                    action();
                }
            }
        });
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

Private data via constructor environments 2

class Countdown {
    constructor(counter, action) {
        this.dec = function dec() {
            if (counter < 1) return;
            counter--;
            if (counter === 0) {
                action();
            }
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

Private data via a naming convention

class Countdown {
    constructor(counter, action) {
        this._counter = counter;
        this._action = action;
    }
    dec() {
        if (this._counter < 1) return;
        this._counter--;
        if (this._counter === 0) {
            this._action();
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

Private data via WeakMaps

const _counter = new WeakMap();
const _action = new WeakMap();
class Countdown {
    constructor(counter, action) {
        _counter.set(this, counter);
        _action.set(this, action);
    }
    dec() {
        let counter = _counter.get(this);
        if (counter < 1) return;
        counter--;
        _counter.set(this, counter);
        if (counter === 0) {
            _action.get(this)();
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

Private data via symbols

const _counter = Symbol('counter');
const _action = Symbol('action');

class Countdown {
    constructor(counter, action) {
        this[_counter] = counter;
        this[_action] = action;
    }
    dec() {
        if (this[_counter] < 1) return;
        this[_counter]--;
        if (this[_counter] === 0) {
            this[_action]();
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

R
Robin F.

Personally I like the proposal of the bind operator :: and would then combine it with the solution @d13 mentioned but for now stick with @d13 's answer where you use the export keyword for your class and put the private functions in the module.

there is one more solution tough which hasn't been mentioned here that follows are more functional approach and would allow it to have all the private props/methods within the class.

Private.js

export const get = state => key => state[key];
export const set = state => (key,value) => { state[key] = value; }

Test.js

import { get, set } from './utils/Private'
export default class Test {
  constructor(initialState = {}) {
    const _set = this.set = set(initialState);
    const _get = this.get = get(initialState);

    this.set('privateMethod', () => _get('propValue'));
  }

  showProp() {
    return this.get('privateMethod')();
  }
}

let one = new Test({ propValue: 5});
let two = new Test({ propValue: 8});
two.showProp(); // 8
one.showProp(); // 5

comments on it would be appreciated.


Generally I like the approach. Feedback: 1. you will need a different private.js module for each class to prevent clashing. 2. I dislike the potential of making the constructor really long by inline-defining each of your private methods. 3. It would be nice if all of the class methods were in one file.
N
NanoWizard

I think Benjamin's answer is probably the best for most cases until the language natively supports explicitly private variables.

However, if for some reason you need to prevent access with Object.getOwnPropertySymbols(), a method I've considered using is attaching a unique, non-configurable, non-enumerable, non-writable property that can be used as a property identifier to each object on construction (such as a unique Symbol, if you don't already have some other unique property like an id). Then just keep a map of each object's 'private' variables using that identifier.

const privateVars = {};

class Something {
    constructor(){
        Object.defineProperty(this, '_sym', {
            configurable: false,
            enumerable: false,
            writable: false,
            value: Symbol()
        });

        var myPrivateVars = {
            privateProperty: "I'm hidden"
        };

        privateVars[this._sym] = myPrivateVars;

        this.property = "I'm public";
    }

    getPrivateProperty() {
        return privateVars[this._sym].privateProperty;
    }

    // A clean up method of some kind is necessary since the
    // variables won't be cleaned up from memory automatically
    // when the object is garbage collected
    destroy() {
        delete privateVars[this._sym];
    }
}

var instance = new Something();
console.log(instance.property); //=> "I'm public"
console.log(instance.privateProperty); //=> undefined
console.log(instance.getPrivateProperty()); //=> "I'm hidden"

The potential advantage of this approach over using a WeakMap is faster access time if performance becomes a concern.


Correct me if I am wrong, but wouldn't this code contain memory leaks since privateVars will still store an object's private variables even if the object is already destroyed?
@RussellSantos you are correct, assuming the objects will need to be garbage collected at some point. Thank you for pointing that out. In my example I've added a destroy() method which should be called by the using code whenever an object needs to be removed.
J
JSInitiate

I believe it is possible to get 'best of both worlds' using closures inside constructors. There are two variations:

All data members are private

function myFunc() { console.log('Value of x: ' + this.x); this.myPrivateFunc(); } function myPrivateFunc() { console.log('Enhanced value of x: ' + (this.x + 1)); } class Test { constructor() { let internal = { x : 2, }; internal.myPrivateFunc = myPrivateFunc.bind(internal); this.myFunc = myFunc.bind(internal); } };

Some members are private

NOTE: This is admittedly ugly. If you know a better solution, please edit this response.

function myFunc(priv, pub) { pub.y = 3; // The Test object now gets a member 'y' with value 3. console.log('Value of x: ' + priv.x); this.myPrivateFunc(); } function myPrivateFunc() { pub.z = 5; // The Test object now gets a member 'z' with value 3. console.log('Enhanced value of x: ' + (priv.x + 1)); } class Test { constructor() { let self = this; let internal = { x : 2, }; internal.myPrivateFunc = myPrivateFunc.bind(null, internal, self); this.myFunc = myFunc.bind(null, internal, self); } };


F
Francisco Neto

In fact it is possible using Symbols and Proxies. You use the symbols in the class scope and set two traps in a proxy: one for the class prototype so that the Reflect.ownKeys(instance) or Object.getOwnPropertySymbols doesn't give your symbols away, the other one is for the constructor itself so when new ClassName(attrs) is called, the instance returned will be intercepted and have the own properties symbols blocked. Here's the code:

const Human = (function() { const pet = Symbol(); const greet = Symbol(); const Human = privatizeSymbolsInFn(function(name) { this.name = name; // public this[pet] = 'dog'; // private }); Human.prototype = privatizeSymbolsInObj({ [greet]() { // private return 'Hi there!'; }, revealSecrets() { console.log(this[greet]() + ` The pet is a ${this[pet]}`); } }); return Human; })(); const bob = new Human('Bob'); console.assert(bob instanceof Human); console.assert(Reflect.ownKeys(bob).length === 1) // only ['name'] console.assert(Reflect.ownKeys(Human.prototype).length === 1 ) // only ['revealSecrets'] // Setting up the traps inside proxies: function privatizeSymbolsInObj(target) { return new Proxy(target, { ownKeys: Object.getOwnPropertyNames }); } function privatizeSymbolsInFn(Class) { function construct(TargetClass, argsList) { const instance = new TargetClass(...argsList); return privatizeSymbolsInObj(instance); } return new Proxy(Class, { construct }); }

Reflect.ownKeys() works like so: Object.getOwnPropertyNames(myObj).concat(Object.getOwnPropertySymbols(myObj)) that's why we need a trap for these objects.


M
Michael Franzl

Even Typescript can't do it. From their documentation:

When a member is marked private, it cannot be accessed from outside of its containing class. For example: class Animal { private name: string; constructor(theName: string) { this.name = theName; } } new Animal("Cat").name; // Error: 'name' is private;

But transpiled on their playground this gives:

var Animal = (function () {
    function Animal(theName) {
        this.name = theName;
    }
    return Animal;
}());
console.log(new Animal("Cat").name);

So their "private" keyword is ineffective.


Well, it's still effective because it prevents "bad" programming, while in the IDE. It shows you which members you should and shouldn't use. I think that's the main reason for using private and public. (For example, when you compile C# to machine code, will private still be private? who knows?). When reading the other answers, it seems that using @Symbol can also make a member inaccessible. But even Symbols can still be found from the console.
Does the TypeScript error occur during the transpile of TypeScript to JavaScript? (Like the type checking happens at transpite time. Rather than some runtime private mechanism.)
Note that this response talks about TypeScript specific private modifier, but TypeScript 4.3 introduced additional support for ECMAScript #private Class Elements that unlike private modifier isn't just private at compile-time, but also at runtime (if compiled to >= ES 2015). I created sample code in TypeScript playfeild to demonstrate, that it is truly private (ESNext target works with Chrome, that has implemented support for ES private fields proposal)
P
Paul Whipp

Coming very late to this party but I hit the OP question in a search so... Yes, you can have private properties by wrapping the class declaration in a closure

There is an example of how I have private methods in this codepen. In the snippet below, the Subscribable class has two 'private' functions process and processCallbacks. Any properties can be added in this manner and they are kept private through the use of the closure. IMO Privacy is a rare need if concerns are well separated and Javascript does not need to become bloated by adding more syntax when a closure neatly does the job.

const Subscribable = (function(){

  const process = (self, eventName, args) => {
    self.processing.set(eventName, setTimeout(() => processCallbacks(self, eventName, args)))};

  const processCallbacks = (self, eventName, args) => {
    if (self.callingBack.get(eventName).length > 0){
      const [nextCallback, ...callingBack] = self.callingBack.get(eventName);
      self.callingBack.set(eventName, callingBack);
      process(self, eventName, args);
      nextCallback(...args)}
    else {
      delete self.processing.delete(eventName)}};

  return class {
    constructor(){
      this.callingBack = new Map();
      this.processing = new Map();
      this.toCallbacks = new Map()}

    subscribe(eventName, callback){
      const callbacks = this.unsubscribe(eventName, callback);
      this.toCallbacks.set(eventName,  [...callbacks, callback]);
      return () => this.unsubscribe(eventName, callback)}  // callable to unsubscribe for convenience

    unsubscribe(eventName, callback){
      let callbacks = this.toCallbacks.get(eventName) || [];
      callbacks = callbacks.filter(subscribedCallback => subscribedCallback !== callback);
      if (callbacks.length > 0) {
        this.toCallbacks.set(eventName, callbacks)}
      else {
        this.toCallbacks.delete(eventName)}
      return callbacks}

    emit(eventName, ...args){
      this.callingBack.set(eventName, this.toCallbacks.get(eventName) || []);
      if (!this.processing.has(eventName)){
        process(this, eventName, args)}}}})();

I like this approach because it separates concerns nicely and keeps things truly private. The only downside is the need to use 'self' (or something similar) to refer to 'this' in the private content.


1
1-14x0r

Yes totally can, and pretty easily too. This is done by exposing your private variables and functions by returning the prototype object graph in the constructor. This is nothing new, but take a bit of js foo to understand the elegance of it. This way does not use global scoped, or weakmaps. It is a form of reflection built into the language. Depending on how you leverage this; one can either force an exception which interrupts the call stack, or bury the exception as an undefined. This is demonstarted below, and can read more about these features here

class Clazz { constructor() { var _level = 1 function _private(x) { return _level * x; } return { level: _level, public: this.private, public2: function(x) { return _private(x); }, public3: function(x) { return _private(x) * this.public(x); }, }; } private(x) { return x * x; } } var clazz = new Clazz(); console.log(clazz._level); //undefined console.log(clazz._private); // undefined console.log(clazz.level); // 1 console.log(clazz.public(1)); //1 console.log(clazz.public2(2)); //2 console.log(clazz.public3(3)); //27 console.log(clazz.private(0)); //error


I
Ilya Zarembsky
class Something {
  constructor(){
    var _property = "test";
    Object.defineProperty(this, "property", {
        get: function(){ return _property}
    });
  }
}

var instance = new Something();
console.log(instance.property); //=> "test"
instance.property = "can read from outside, but can't write";
console.log(instance.property); //=> "test"

It's best to avoid code only answers. It would be better if you could explain how your code answers the OP's question
This is really how to make a readonly variable more than a private variable. A private variable should not be accessible to the outside. console.log(instance.property) should throw or give you undefined, not give you back "test".
J
Jayesbe

Another way similar to the last two posted

class Example {
  constructor(foo) {

    // privates
    const self = this;
    this.foo = foo;

    // public interface
    return self.public;
  }

  public = {
    // empty data
    nodata: { data: [] },
    // noop
    noop: () => {},
  }

  // everything else private
  bar = 10
}

const test = new Example('FOO');
console.log(test.foo); // undefined
console.log(test.noop); // { data: [] }
console.log(test.bar); // undefined

N
Nikola Andreev

I found a very simple solution, just use Object.freeze(). Of course the problem is you can't add nothing to the object later.

class Cat {
    constructor(name ,age) {
        this.name = name
        this.age = age
        Object.freeze(this)
    }
}

let cat = new Cat('Garfield', 5)
cat.age = 6 // doesn't work, even throws an error in strict mode

this will also disable setter method like setName(name) { this.name = name; }
j
johny why

This code demonstrates private and public, static and non-static, instance and class-level, variables, methods, and properties.

https://codesandbox.io/s/class-demo-837bj

class Animal { static count = 0 // class static public static #ClassPriVar = 3 // class static private constructor(kind) { this.kind = kind // instance public property Animal.count++ let InstancePriVar = 'InstancePriVar: ' + kind // instance private constructor-var log(InstancePriVar) Animal.#ClassPriVar += 3 this.adhoc = 'adhoc' // instance public property w/out constructor- parameter } #PawCount = 4 // instance private var set Paws(newPawCount) { // instance public prop this.#PawCount = newPawCount } get Paws() { // instance public prop return this.#PawCount } get GetPriVar() { // instance public prop return Animal.#ClassPriVar } static get GetPriVarStat() { // class public prop return Animal.#ClassPriVar } PrintKind() { // instance public method log('kind: ' + this.kind) } ReturnKind() { // instance public function return this.kind } /* May be unsupported get #PrivMeth(){ // instance private prop return Animal.#ClassPriVar + ' Private Method' } static get #PrivMeth(){ // class private prop return Animal.#ClassPriVar + ' Private Method' } */ } function log(str) { console.log(str) } // TESTING log(Animal.count) // static, avail w/out instance log(Animal.GetPriVarStat) // static, avail w/out instance let A = new Animal('Cat') log(Animal.count + ': ' + A.kind) log(A.GetPriVar) A.PrintKind() A.Paws = 6 log('Paws: ' + A.Paws) log('ReturnKind: ' + A.ReturnKind()) log(A.adhoc) let B = new Animal('Dog') log(Animal.count + ': ' + B.kind) log(B.GetPriVar) log(A.GetPriVar) // returns same as B.GetPriVar. Acts like a class-level property, but called like an instance-level property. It's cuz non-stat fx requires instance. log('class: ' + Animal.GetPriVarStat) // undefined log('instance: ' + B.GetPriVarStat) // static class fx log(Animal.GetPriVar) // non-stat instance fx log(A.InstancePriVar) // private log(Animal.InstancePriVar) // private instance var log('PawCount: ' + A.PawCount) // private. Use getter /* log('PawCount: ' + A.#PawCount) // private. Use getter log('PawCount: ' + Animal.#PawCount) // Instance and private. Use getter */


a
asdru

Reading the previous answer i thought that this example can summarise the above solutions

const friend = Symbol('friend');

const ClassName = ((hidden, hiddenShared = 0) => {

    class ClassName {
        constructor(hiddenPropertyValue, prop){
            this[hidden] = hiddenPropertyValue * ++hiddenShared;
            this.prop = prop
        }

        get hidden(){
            console.log('getting hidden');
            return this[hidden];
        }

        set [friend](v){
            console.log('setting hiddenShared');
            hiddenShared = v;
        }

        get counter(){
            console.log('getting hiddenShared');
            return hiddenShared;
        }

        get privileged(){
            console.log('calling privileged method');
            return privileged.bind(this);
        }
    }

    function privileged(value){
        return this[hidden] + value;
    }

    return ClassName;
})(Symbol('hidden'), 0);

const OtherClass = (() => class OtherClass extends ClassName {
    constructor(v){
        super(v, 100);
        this[friend] = this.counter - 1;
    }
})();

UPDATE

now is it possible to make true private properties and methods (at least on chrome based browsers for now).

The syntax is pretty neat

class MyClass {
    #privateProperty = 1
    #privateMethod() { return 2 }
    static #privateStatic = 3
    static #privateStaticMethod(){return 4}
    static get #privateStaticGetter(){return 5}

    // also using is quite straightforward
    method(){
        return (
            this.#privateMethod() +
            this.#privateProperty +
            MyClass.#privateStatic +
            MyClass.#privateStaticMethod() +
            MyClass.#privateStaticGetter
        )
    }
}

new MyClass().method()
// returns 15

Note that for retrieving static references you wouldn't use this.constructor.#private, because it would brake its subclasses. You must use a reference to the proper class in order to retrieve its static private references (that are available only inside the methods of that class), ie MyClass.#private.


g
guitarino

Most answers either say it's impossible, or require you to use a WeakMap or Symbol, which are ES6 features that would probably require polyfills. There's however another way! Check out this out:

// 1. Create closure var SomeClass = function() { // 2. Create `key` inside a closure var key = {}; // Function to create private storage var private = function() { var obj = {}; // return Function to access private storage using `key` return function(testkey) { if(key === testkey) return obj; // If `key` is wrong, then storage cannot be accessed console.error('Cannot access private properties'); return undefined; }; }; var SomeClass = function() { // 3. Create private storage this._ = private(); // 4. Access private storage using the `key` this._(key).priv_prop = 200; }; SomeClass.prototype.test = function() { console.log(this._(key).priv_prop); // Using property from prototype }; return SomeClass; }(); // Can access private property from within prototype var instance = new SomeClass(); instance.test(); // `200` logged // Cannot access private property from outside of the closure var wrong_key = {}; instance._(wrong_key); // undefined; error logged

I call this method accessor pattern. The essential idea is that we have a closure, a key inside the closure, and we create a private object (in the constructor) that can only be accessed if you have the key.

If you are interested, you can read more about this in my article. Using this method, you can create per object properties that cannot be accessed outside of the closure. Therefore, you can use them in constructor or prototype, but not anywhere else. I haven't seen this method used anywhere, but I think it's really powerful.


The question was about how to achieve this in ES6 classes.
You can use the exact same method in ES6 classes. ES6 classes is mainly just sugar on top of functions like I presented in my example. It's quite possible that the original poster is using a transpiler, in which case WeakMaps or Symbols will still require polyfills. My answer is valid regardless.
k
kofifus

See this answer for a a clean & simple 'class' solution with a private and public interface and support for composition


Y
Yami Teru

I use this pattern and it's always worked for me

class Test { constructor(data) { class Public { constructor(prv) { // public function (must be in constructor on order to access "prv" variable) connectToDb(ip) { prv._db(ip, prv._err); } } // public function w/o access to "prv" variable log() { console.log("I'm logging"); } } // private variables this._data = data; this._err = function(ip) { console.log("could not connect to "+ip); } } // private function _db(ip, err) { if(!!ip) { console.log("connected to "+ip+", sending data '"+this.data+"'"); return true; } else err(ip); } } var test = new Test(10), ip = "185.167.210.49"; test.connectToDb(ip); // true test.log(); // I'm logging test._err(ip); // undefined test._db(ip, function() { console.log("You have got hacked!"); }); // undefined