16

in es5 we use constructor function

function Person(name,gender){

    var initial ="";    // we use var key word to make variable private

    function getNameWithInitial(){ // this is the private method to get name with initial
        console.log(this);
        initial = this.gender ==="male"?"Mr. ":"Mrs. ";
        return initial + this.name;
    }


    this.name = name;
    this.gender = gender;
    this.getName = function(){
        return getNameWithInitial.call(this);
    }


}


var manas = new Person("Manas","male");

console.log(manas.getName());

My question is how to declare a private variable and private method in es6 class

manas
  • 6,119
  • 10
  • 45
  • 56
  • there are no private methods in js, though you can have local variables/functions. – dandavis Jan 19 '16 at 05:51
  • the code you show demonstrates a `protected` method, not a `private` method. at any rate, the old way still works in ES6. – dandavis Jan 19 '16 at 06:17
  • "the old way still works in ES6 " ---- can you show me an example of creating a so called protected method in es6 – manas Feb 11 '17 at 01:34

4 Answers4

21

One way to achieve this is using another ES2015 feature known as modules.

You may already be familiar with AMD modules, or commonJS modules (used by Nodejs). Well ES6 / ES2015 brings a standard for JS - we'll call them ES6 modules but they are part of the JS language now. Once you have modules, you have the ability to do information hiding for both private functions and object variables. Bear in mind, only what you "export" is visible to client calling code.

Lets work through your example code. Here is a first cut:

person.js

 const getNameWithInitial = function () {
      let initial = this._gender === 'male' ?
      'Mr. ' :
      'Mrs. ';
      return initial + this._name;
    }

  export class Person {

    constructor(name, gender) {
        this._name = name;
        this._gender = gender;
      }

      get name() {
          return getNameWithInitial.call(this);
        }
    }
  }

client.js

import {Person} from './person';
const manas = new Person('Manas', 'male');
console.log(manas.name);  // this calls what was your getName function

Now, the getNameWithInitial function is effectively private, as it is not exported, so client.js cannot see it.

However, we still have a problem for the Person class, since this is exported. At the moment you can just walk up to manas object and do:

manas._name = 'Joe'

With properties like _name, we can combine modules and symbols. This is a powerful yet lightweight information hiding technique available with ES6+/ES2015.

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.

Let's see our modified code to make use of symbols and modules to hide Class attributes.

person.js

const s_name = Symbol();
const s_gender = Symbol();

const getNameWithInitial = function () {
  let initial = this[s_gender] === 'male' ?
    'Mr. ' :
    'Mrs. ';
  return initial + this[s_name];
}

export class Person {

  constructor(name, gender) {
    this[s_name] = name;
    this[s_gender] = gender;
  }

  get name() {
    return getNameWithInitial.call(this);
  }
}

So, now a client cannot just do:

 manas._name = 'Joe' 

because _name is not being used as the key for the name value.

However, symbols are exposed via reflection features such as Object.getOwnPropertySymbols so be aware they are not "completely' private using this technique.

import {Person} from './person';
const manas = new Person('Manas', 'male');
const vals = Object.getOwnPropertySymbols(manas);
manas[vals[0]] = 'Joanne';
manas[vals[1]] = 'female';

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 strictly private). Using modules today is available with build tools eg. webpack / browserify and babel.

arcseldon
  • 35,523
  • 17
  • 121
  • 125
  • 1
    But what about: `Object.getOwnPropertySymbols( person ).map( symbol => person[symbol] ); // [ name, gender ]` Sure, the key names are no longer human-readable, but they're not any more machine-safe than `Object.keys(person).map( key => person[key] );` either. Do not use this method, if you are storing data that you intend to keep hidden in a closure, and it *really* does need to stay *hidden*. – Norguard Jan 19 '16 at 11:59
  • Updated answer - you are right, this is an effective information hiding strategy only, it does not guarantee privacy. The Babel docs state this clearly - https://babeljs.io/docs/learn-es2015/ – arcseldon Jan 19 '16 at 13:43
  • 2
    Why can't you simply declare `var name` and `var gender` outside the class `Person`, then access them from within `Person`'s functions? Those vars would not be exported, so wouldn't they be truly private? – Jez Sep 17 '16 at 20:55
  • 2
    @Jez because every instance of `Person` that is instantiated with the `new` operator would be creating a closure over the same `name` and `gender` references, causing different instances to operate on the same data which would produce unexpected results. – leo Mar 15 '17 at 21:01
  • @leo You place those private variables within a closure that the class is also a part of. Then, any instances created will have their own private variables. Better yet, use "let" instead of var to create those variables. – Johann May 24 '17 at 06:11
  • @AndroidDev Then you would need each instance to have it's own method (closure), because you can't create a closure on a prototype that is also able to access variables declared in the constructor. – Armen Michaeli Dec 08 '18 at 22:03
5

If you would like an analogue to the ES5 solution, it's quite simple; the constructor simply becomes what holds the closure, and you add any methods/objects which should remember the private state in there.
Prototyped methods have no access to the closure of the initial constructor, without using some privileged getters:

class Person {

  constructor ({ name, age, deepDarkSecret }) {
    const bribe = () => console.log(`Give me money, or everybody will know about ${ redactRandom(deepDarkSecret) }`);

    Object.assign(this, { name, age }); // assigning publicly accessible values
    Object.assign(this, { bribe }); // assign "privileged" methods (functions with closure-reference to initial values)
  }

  recallSecret () {
    console.log("I'm just a prototyped method, so I know nothing about any secret, unless I use a privileged function...");
    this.bribe();
  }
}

If you look at what this nets you, you'll note that it's really not much different than your example (just using "cleaner" bells and whistles; especially when adding prototype/static methods). It's still prototype underneath.

If you have the luxury of using any kind of module/export (ES6 being the ideal), then with one other data-type, you can have true privacy and not have to clean up after yourself.

It's a little hackey-looking. It'll probably get less ugly, and hopefully be the basis for something cleaner, in the future, but if you want instance-based, private access, even for prototyped methods, then make one WeakMap, inside of the module that you're exporting your class from.

Use this as your key. Rather than making privileged functions which have closure access, now all prototype methods have closure access to the weakmap...
...the downside being that any time you want private variables, you have to look them up in the WeakMap rather than pulling them off of / out of this.

const personalBaggage = new WeakMap();

class Person {
  constructor ({ name, age, darkestMoment }) {
    const privates = { name, age, darkestMoment };
    personalBaggage.add(this, privates);
  }

  recallDarkestHour () {
    const { darkestMoment } = personalBaggage.get(this);
    console.log(darkestMoment);
  }
}

export default Person;

As long as you aren't losing the reference inside of this, this should just work.
Unlike using Symbol, you can't get a reference to the private object, no matter how hard you try.
If you know the symbols on the object, you can look up the properties using the symbols.
And getting the list of symbols on any object is just a function call away. Symbol isn't about keeping things private; it's about keeping things safe from naming collisions, and defining common symbols that can be used on any object/function, but won't be picked up in loops, won't be called or overwritten accidentally, et cetera.

Storing the private bag of data in a weakmap with this as a key gives you access to a completely hidden dataset, which is totally unaccessible outside of that module.
What's better, the key/value in weakmaps don't prevent the GC from cleaning them up.
Normally, if they were left in an array, they'd stay there. If the last place an object is used is as a key in a weakmap, then it gets collected and the weakmap value is erased automatically (in native ES6; the memory bonuses can't be polyfilled, but the object can).

Norguard
  • 26,167
  • 5
  • 41
  • 49
  • Very similar to answer given here: http://stackoverflow.com/a/34418487/1882064 – arcseldon Jan 19 '16 at 11:08
  • @arcseldon It is quite close; that said, in the accepted answer, you'd have to create a WeakMap instance for each and every member / method you wanted to hide, whereas in my example, you're creating just one map and destructuring what you need from it. Both have their pros and cons, of course. This feels a little less unwieldy to me. – Norguard Jan 19 '16 at 11:48
0

here is a really good post on the subject. I didn't know much about doing this in ES6 before your question but after reviewing this it looks really cool. So you create a symbol that is indexed to the function.

here is the code straight from their sit.

var Person = (function() {
    var nameSymbol = Symbol('name');

    function Person(name) {
        this[nameSymbol] = name;
    }

    Person.prototype.getName = function() {
        return this[nameSymbol];
    };

    return Person;
}());

var p = new Person('John');

Private properties in JavaScript

andre mcgruder
  • 1,120
  • 1
  • 9
  • 12
  • 2
    They're not unreachable from the outside; they're unreachable by people attempting to get to them via text names, and they aren't included in iterations, but you can still get all of the keys off the object, inspect the keys in a `.toString` and use the list of keys to look up the values. Symbol is very useful, just don't think it buys you safety in a project that relies on closures to stay closed to prevent tampering. – Norguard Jan 19 '16 at 12:03
0

You may accomplish it using Symbol.

const _name = Symbol();

class Person {
  constructor(name) {
    this[_name] = name;
  } 
}

const person = new Person('John');

alert(person.name);
// => undefined
jdlm
  • 6,327
  • 5
  • 29
  • 49
  • 2
    `Object.getOwnPropertySymbols( person ).map( symbol => person[symbol] ); // ["John"]` Not saying it's not extremely useful for other things... but at the same time, it's not extremely private, if that's what you're going for. – Norguard Jan 19 '16 at 11:51