• Stars
    star
    107
  • Rank 323,587 (Top 7 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 10 years ago
  • Updated almost 10 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A simple and intuitive way to shim private methods and properties in JavaScript.

Private Parts

Build Status

  1. Introduction
  2. How It Works
  3. API Documentation
  4. Installation
  5. Browser and Environment Support
  6. Building and Testing

The Private Parts module provides a simple and intuitive way to shim private methods and properties in JavaScript. It's small, easy to use, requires minimal setup, and works in both node and the browser.

For more information on how Private Parts works and the problems it solves, see my article introducing it.

Introduction

Most people deal with private properties in JavaScript by prefixing them with an underscore and hoping that everyone using their library understands and respects this convention.

Consider the following example:

function Car() {
  this._mileage = 0;
}

Car.prototype.drive = function(miles) {
  if (typeof miles == 'number' && miles > 0) {
    this._mileage += miles;
  } else {
    throw new Error('drive only accepts positive numbers');
  }
}

Car.prototype.readMileage = function() {
  return this._mileage;
}

This is okay, but anyone familiar with JavaScript will easily spot the problem: the validation check in the drive method is essentially useless. Anyone with access to the Car instance could easily set _mileage to whatever they want.

var honda = new Car();
honda._mileage = 'pwned';

A Better Way

Here's how you solve this problem and get real privacy using Private Parts. Notice that the code is almost exactly the same:

var _ = require('private-parts').createKey();

function Car() {
  _(this).mileage = 0;
}

Car.prototype.drive = function(miles) {
  if (typeof miles == 'number' && miles > 0) {
    _(this).mileage += miles;
  } else {
    throw new Error('drive only accepts positive numbers');
  }
}

Car.prototype.readMileage = function() {
  return _(this).mileage;
}

The first example used this._mileage to reference the "private" mileage property of each instance. In the second example, all occurrences of this._mileage have been replaced with _(this).mileage. As a result, mileage is never actually a property of this, so it can't be tampered with.

var honda = new Car();
console.log(honda.mileage); // undefined

How it works

If you look at the Private Parts example in the code above, you'll notice that the this context is wrapped in the _() function whenever it needs to access private data.

I call _() the "key function" or often just the "key".

The Key Function

The key function is very simple to use. It accepts an object and returns a new object that is uniquely linked to the passed object, yet inaccessible without the key function itself. From here on, I will refer to the passed object as the "public instance" and the returned object as the "private instance".

You can create the key function by calling createKey(), the sole method provided by the Private Parts module. I usually assign the key to the _ variable (since an underscore is only one character and often used to denote privacy) but you can choose any variable you like.

The key (like any variable in JavaScript) is only accessible to the scope it's defined in. This is what makes private properties possible. The key has access to the private instance, but outside code does not have access to the key.

Using the Key

The first step is to create the key. Make sure to pay attention to the scope you're in. If you're in the browser, make sure you don't accidentally expose the key to the global scope.

The second step is to use the key to get and set properties. Any time you want a property to be private, use the key to set that property on the private instance. Since it's actually private, you'll need to create getters and setters to access it from any public scope.

var _ = require('private-parts').createKey();

function SomeClass() {
  // `privateProperty` is not accessible outside this module
  _(this).privateProperty = 'bar';
}

SomeClass.prototype.getPrivateProperty = function() {
  return _(this).privateProperty;
}

SomeClass.prototype.setPrivateProperty = function(value) {
  return _(this).privateProperty = value;
}

Note that you don't need to check if the private instance exists before using it. The key function automatically creates a private instance if one doesn't exist, and it returns the private instance if it does.

Customizing the Private Instance

When you pass a public instance to the key function and get a private instance back, the private instance (by default) will be a plain old JavaScript object.

var _ = require('private-parts').createKey();

// The prototype chain will look like this:
_(this)  >>>  Object.prototype

This is okay for most situations, but sometimes you want a bit more control over how your private instances are created. Most commonly, you'll want to initialize some properties or set their prototype.

To specify how the private instances are created, you can pass a creator function to the createKey method. This creator function will be invoked with the public instance as its first argument and the private instance will be the return value.

For example, if you want the private instance to have a reference back to the public instance, you could do something like this:

var _ = createKey(function(publicInstance) {
  return { __public__: publicInstance };
})

_({foo:'bar'}) // returns { __public__: {foo:'bar'}}

Perhaps the most common customization need for private instances is to set their prototype at creation time. This is allows you to let all private instances share a common set of methods — effectively a private prototype.

You can do this by passing an object to createKey instead of a function. When an object is passed, new instances are created via Object.create and your passed object is used as the prototype. The following example illustrates this.

// These two statements are equivalent.
var _ = createKey(someObj);
var _ = createKey(Object.create.bind(null, someObj, {}));

// The prototype chain now looks like this:
_(this)  >>>  someObj

This technique can be very powerful. If you create a private object from the constructor prototype (using Object.create), set some methods on it, and then pass that object to createKey you'll end up with private instances that have both the private and public methods in their prototype chain. Here's an example.

var privateMethods = Object.create(SomeClass.prototype);
privateMethods.privateMethodOne = function() {...}
privateMethods.privateMethodTwo = function() {...}

var _ = require('private-parts').createKey(privateMethods);

// The prototype chain now looks like this:
_(this)  >>>  privateMethods  >>>  SomeClass.prototype

// And public instances will not be able to see private methods:
this  >>>  SomeClass.prototype

For a complete example illustrating this technique, check out the Car fixture in the tests directory. This example class is what many of the tests are based on.

And for a more out-of-the-box solution, check out the Mozart library, a classical inheritance implementation that uses Private Parts to acheive public, protected, and private methods in its class heirarchies.

API Documentation

_(obj)

The key function, usually stored on the _ (underscore) variable, acts as an accessor to the private store. It accepts an object (the "public instance") and returns the object associated with that passed object (the "private instance"). If no private instance counterpart exists, a new one is created.

The method in which new private instances are created is determined by the argument passed to the createKey factory method, as described next:

createKey(fn)

When createKey is passed a function, that function is used to create new private instances. The passed function (the creator function) is invoked with the public instance as its first argument. The return value of the creator function becomes the private instance.

createKey(obj)

When createKey receives an object instead of a function, it actually creates a function behind the scenes by binding the passed object to Object.create. This effectively means that newly created instances will have the object passed to createKey as their prototype.

createKey()

If nothing is passed to createKey, a plain old JavaScript object is created.

Installation

Private Parts is incredibly small. It's less than 1K minified and gzipped.

To install from NPM:

npm install --save private-parts

From Bower:

bower install --save private-parts

Browser and Environment Support

Private Parts has been tested and known to work in the following environments. Older browser support (including IE8 and lower) is likely possible with the right polyfills.

  • Node.js
  • Chrome 6+
  • Firefox 4+
  • Safari 5.1+
  • Internet Explorer 9+
  • Opera 12+

Private Parts works in both Node and the browser. It uses the UMD pattern, so it can be included in your application as either an AMD module or a the global variable PrivateParts.

It's important to note that Private Parts uses the ES6 WeakMap data structure. If you need to support an environment without WeakMaps, you can still use Private Parts, you just have to include one of the many available polyfills. I use this WeakMap Polyfill by Brandon Benvie of Mozilla, which gives me the browser support I list above. If you need better support you should use a different polyfill along with an ES5 shim (for IE8 and lower).

For a list of environments that support WeakMap natively, see Kangax's ES6 compatibility tables.

Usage Examples

In node:

var _ = require('private-parts').createKey();

function Car() {
  _(this).mileage = 0;
}

// ...

module.exports = Car;

In the browser via AMD:

define(['parth/to/private-parts'], function(PrivateParts) {

  var _ = PrivateParts.createKey();

  function Car() {
    _(this).mileage = 0;
  }

  // ...

  return Car;
})

In the browser via globals:

var Car = (function() {

  var _ = PrivateParts.createKey();

  function Car() {
    _(this).mileage = 0;
  }

  // ...

  return Car;
}());

With a Polyfill

In node:

// Put this code year the main entry point of your app.
if (!('WeakMap' in global)) global.WeakMap = require('weakmap');

In the browser:

<!-- needed for most browsers -->
<script src="path/to/weakmap.js"></script>

<!-- if you need to support really old browsers -->
<script src="path/to/es5-shim.js"></script>

<script src="path/to/private-parts.js"></script>

Building and Testing

To run the tests and build the browser version of the library, use the following commands:

# Run the node and browser tests.
make test

# Build the browser version.
make build

# Test and build.
make

Private Parts uses Browserify to build the browser version of the library as well as browser versions of the tests. It uses Travic-CI to run the tests in Node.js and Testling to run the tests in actual browsers on each commit.

More Repositories

1

flexbugs

A community-curated list of flexbox issues and cross-browser workarounds for them.
13,629
star
2

solved-by-flexbox

A showcase of problems once hard or impossible to solve with CSS alone, now made trivially easy with Flexbox.
CSS
13,009
star
3

html-inspector

HTML Inspector is a code quality tool to help you and your team write better markup. It's written in JavaScript and runs in the browser, so testing your HTML has never been easier.
JavaScript
2,317
star
4

analyticsjs-boilerplate

Examples and best practices for using analytics.js
JavaScript
1,128
star
5

responsive-components

A modern approach to styling elements based on the size of their container
JavaScript
586
star
6

webpack-esnext-boilerplate

JavaScript
513
star
7

polyfill

A library to make creating CSS polyfills much easier.
JavaScript
296
star
8

rollup-native-modules-boilerplate

A demo app showcasing the use of real JavaScript modules in production—complete with cross-browser fallbacks for legacy browsers.
JavaScript
223
star
9

blog

The source code for https://philipwalton.com
JavaScript
183
star
10

mozart

A full-featured, classical inheritance library for Node.js and the browser.
JavaScript
84
star
11

easy-sauce

Easily run JavaScript unit tests on the Sauce Labs cloud.
JavaScript
74
star
12

talks

HTML
46
star
13

dom-utils

A small, modular DOM utility library
JavaScript
39
star
14

import-maps-caching-demos

Demos showing how to use Import Maps to prevent cascading cache invalidations
JavaScript
22
star
15

selectable

Easily get and set the text selection with an HTML element.
JavaScript
16
star
16

router

A simple router that binds URLs paths and patterns to functions.
JavaScript
9
star
17

shimr

A proof-of-concept for building CSS polyfills
JavaScript
8
star
18

rollup-built-in-modules

A demo of using rollup with built-in modules
JavaScript
8
star
19

ingen

JavaScript
7
star
20

google-analytics-browser-stats

JavaScript
6
star
21

dotfiles

OS X dotfiles, config, bash, git, etc.
Vim Script
6
star
22

dev

HTML
5
star
23

deep-watch

Exactly like fs.watch, but with sub-directory support.
JavaScript
5
star
24

slide-deck

HTML
4
star
25

deeplinker

JavaScript
4
star
26

handbrake

JavaScript
4
star
27

usage-trends

JavaScript
4
star
28

navigation-event-proposal

3
star
29

solarized-dark-minimalist-syntax

CSS
3
star
30

github-stars

JavaScript
2
star
31

rollup-3245-repro

JavaScript
2
star
32

yore

Async and error handling sugar on top of the HTML5 History api.
JavaScript
2
star
33

photo-validator

JavaScript
2
star
34

philipwalton.github.io

GitHub pages root
HTML
1
star