# ES6 Class Privates

Provides a method of converting underscored pseudo-private class members into
truly private class members via `WeakMap`.


## Status

Developer preview / proof of concept / idea phase.

Not production tested, yet.  Interface could change drastically, and I plan on
adding an admittedly hacked way of doing protected methods as well.


## Requirements and Install

Currently requires node.js >= 6.0.0

```sh
npm install --save es6-class-privates
```

## Why?

In many Javascript projects, so-called private/protected methods are actually
public methods prefixed with an underscore. This may tempt even good-intending
developers into violating encapsulation for the short-term gains despite the
long-term fragility it creates. In fact, a central tenet of object-oriented
programming is that private/protected class internals should be able to change
without breaking anything other than that class itself (and for protected class
members, child-classes as well).

When a private or protected class member is accessed directly outside of the
class (which violates encapsulation), the dependent code will break when the
private/protected class internals are removed, renamed, or otherwise change
behavior. This practice therefore leads to fragility, and should be discouraged.

**Eric Elliott**: "Underscores are a bad idea. Newbies don't know what they mean,
and advanced users think they don't apply to them. Changes break things."
[(1)](https://twitter.com/_ericelliott/status/854532899239886848)

**Joe Lencioni from Airbnb** (on why underscored pseudo-privates are bad): "They
give people a false sense of 'private'ness that could lead to bugs."
[(2)](https://github.com/airbnb/javascript/issues/1024)

**Jordan Harband from Airbnb** (same discussion): "Private means inaccessible. Your
intent to privacy is irrelevant if the value is reachable, ie public. For
example, **npm broke node once by removing an underscore-prefixed variable**."
[(2)](https://github.com/airbnb/javascript/issues/1024)

Unfortunately, once you've got a codebase filled with underscored pseudo-privates,
it can be difficult to find a workable path back to true private class members.

Moving private methods into module scope and calling with a modified context
(via `call`, `apply`, or the new bind operator `::`) is a great approach, but
some people feel it leaves these methods disconnected from the class.

Typescript has `private` and `protected` keywords, and they work great, but
switching from Javascript to Typescript is a major undertaking and buy-in can be
difficult to gain from stakeholders.

Symbols work well, but can feel kludgy.  WeakMaps work well for private state,
but are very awkward for private methods.  And all of these solutions require a
ton of work to implement.

This package is intended to be an _easy_ and _pragmatic_ solution to this
all-too-common problem.


## General use

```
const my = require('es6-class-privates').makeMine();

class HelloWorld {
  constructor() {
    my.bindAllTo(this);
    my(this).privateProperty = 'Hello world';
    my(this).privateMethod();
  }

  _privateMethod() {
    console.log(my(this).privateProperty);
  }
}

module.exports = my.restrict(HelloWorld);
```


## API

### `makeMine(config : Object)`

Produces a WeakMap-based method that returns a map for the Object given.
If an argument is provided for the `config` parameter, this will be merged with
the global configuration to produce a local configuration specific to this
instance of `my`.  See the "Configuration options" section below for more
information on the `config` parameter.

Throws Error when `config` is invalid.


#### Example
For a simple example, see "General Use" section, above.  A more advanced example
follows:

```
const my = require('es6-class-privates').makeMine({
  protectedPrefix: '_',
  privatePrefix: '__',
  dropPrefix: false,
});
```


### `configure(config : Object)`

Allows global reconfiguration of this package's behavior.  All configuration
options are optional, and excluded properties will not modify the configuration.
All prefixes must be non-zero-length strings or `undefined`.  Prefixes cannot
match.

Throws Error when `config` is invalid.


#### Configuration options

* `protectedPrefix` : `String`
The characters at the beginning of a class member name indicating that it
is `protected`.  Default value is `'$'`.

* `privatePrefix` : `String`
The characters at the beginning of a class member name indicating that it
is `private`.  Default value is `'_'`.

* `dropPrefix` : `Boolean`
Whether to include the class member name prefix when restricting the class.
Default value is `true`.


#### Example
```
require('es6-class-privates').configure({
  protectedPrefix: '_',
  privatePrefix: '__',
  dropPrefix: false,
});
```


## Run tests

```
git clone git@github.com:dash-/es6-class-privates.git
cd es6-class-privates
npm test
```
