Projekt

Obecné

Profil

Stáhnout (7.87 KB) Statistiky
| Větev: | Revize:
1
# private [![Build Status](https://travis-ci.org/benjamn/private.png?branch=master)](https://travis-ci.org/benjamn/private) [![Greenkeeper badge](https://badges.greenkeeper.io/benjamn/private.svg)](https://greenkeeper.io/)
2

    
3
A general-purpose utility for associating truly private state with any JavaScript object.
4

    
5
Installation
6
---
7

    
8
From NPM:
9

    
10
    npm install private
11

    
12
From GitHub:
13

    
14
    cd path/to/node_modules
15
    git clone git://github.com/benjamn/private.git
16
    cd private
17
    npm install .
18

    
19
Usage
20
---
21
**Get or create a secret object associated with any (non-frozen) object:**
22
```js
23
var getSecret = require("private").makeAccessor();
24
var obj = Object.create(null); // any kind of object works
25
getSecret(obj).totallySafeProperty = "p455w0rd";
26

    
27
console.log(Object.keys(obj)); // []
28
console.log(Object.getOwnPropertyNames(obj)); // []
29
console.log(getSecret(obj)); // { totallySafeProperty: "p455w0rd" }
30
```
31
Now, only code that has a reference to both `getSecret` and `obj` can possibly access `.totallySafeProperty`.
32

    
33
*Importantly, no global references to the secret object are retained by the `private` package, so as soon as `obj` gets garbage collected, the secret will be reclaimed as well. In other words, you don't have to worry about memory leaks.*
34

    
35
**Create a unique property name that cannot be enumerated or guessed:**
36
```js
37
var secretKey = require("private").makeUniqueKey();
38
var obj = Object.create(null); // any kind of object works
39

    
40
Object.defineProperty(obj, secretKey, {
41
  value: { totallySafeProperty: "p455w0rd" },
42
  enumerable: false // optional; non-enumerability is the default
43
});
44

    
45
Object.defineProperty(obj, "nonEnumerableProperty", {
46
  value: "anyone can guess my name",
47
  enumerable: false
48
});
49

    
50
console.log(obj[secretKey].totallySafeProperty); // p455w0rd
51
console.log(obj.nonEnumerableProperty); // "anyone can guess my name"
52
console.log(Object.keys(obj)); // []
53
console.log(Object.getOwnPropertyNames(obj)); // ["nonEnumerableProperty"]
54

    
55
for (var key in obj) {
56
  console.log(key); // never called
57
}
58
```
59
Because these keys are non-enumerable, you can't discover them using a `for`-`in` loop. Because `secretKey` is a long string of random characters, you would have a lot of trouble guessing it. And because the `private` module wraps `Object.getOwnPropertyNames` to exclude the keys it generates, you can't even use that interface to discover it.
60

    
61
Unless you have access to the value of the `secretKey` property name, there is no way to access the value associated with it. So your only responsibility as secret-keeper is to avoid handing out the value of `secretKey` to untrusted code.
62

    
63
Think of this style as a home-grown version of the first style. Note, however, that it requires a full implementation of ES5's `Object.defineProperty` method in order to make any safety guarantees, whereas the first example will provide safety even in environments that do not support `Object.defineProperty`.
64

    
65
Rationale
66
---
67

    
68
In JavaScript, the only data that are truly private are local variables
69
whose values do not *leak* from the scope in which they were defined.
70

    
71
This notion of *closure privacy* is powerful, and it readily provides some
72
of the benefits of traditional data privacy, a la Java or C++:
73
```js
74
function MyClass(secret) {
75
    this.increment = function() {
76
        return ++secret;
77
    };
78
}
79

    
80
var mc = new MyClass(3);
81
console.log(mc.increment()); // 4
82
```
83
You can learn something about `secret` by calling `.increment()`, and you
84
can increase its value by one as many times as you like, but you can never
85
decrease its value, because it is completely inaccessible except through
86
the `.increment` method. And if the `.increment` method were not
87
available, it would be as if no `secret` variable had ever been declared,
88
as far as you could tell.
89

    
90
This style breaks down as soon as you want to inherit methods from the
91
prototype of a class:
92
```js
93
function MyClass(secret) {
94
    this.secret = secret;
95
}
96

    
97
MyClass.prototype.increment = function() {
98
    return ++this.secret;
99
};
100
```
101
The only way to communicate between the `MyClass` constructor and the
102
`.increment` method in this example is to manipulate shared properties of
103
`this`. Unfortunately `this.secret` is now exposed to unlicensed
104
modification:
105
```js
106
var mc = new MyClass(6);
107
console.log(mc.increment()); // 7
108
mc.secret -= Infinity;
109
console.log(mc.increment()); // -Infinity
110
mc.secret = "Go home JavaScript, you're drunk.";
111
mc.increment(); // NaN
112
```
113
Another problem with closure privacy is that it only lends itself to
114
per-instance privacy, whereas the `private` keyword in most
115
object-oriented languages indicates that the data member in question is
116
visible to all instances of the same class.
117

    
118
Suppose you have a `Node` class with a notion of parents and children:
119
```js
120
function Node() {
121
    var parent;
122
    var children = [];
123

    
124
    this.getParent = function() {
125
        return parent;
126
    };
127

    
128
    this.appendChild = function(child) {
129
        children.push(child);
130
        child.parent = this; // Can this be made to work?
131
    };
132
}
133
```
134
The desire here is to allow other `Node` objects to manipulate the value
135
returned by `.getParent()`, but otherwise disallow any modification of the
136
`parent` variable. You could expose a `.setParent` function, but then
137
anyone could call it, and you might as well give up on the getter/setter
138
pattern.
139

    
140
This module solves both of these problems.
141

    
142
Usage
143
---
144

    
145
Let's revisit the `Node` example from above:
146
```js
147
var p = require("private").makeAccessor();
148

    
149
function Node() {
150
    var privates = p(this);
151
    var children = [];
152

    
153
    this.getParent = function() {
154
        return privates.parent;
155
    };
156

    
157
    this.appendChild = function(child) {
158
        children.push(child);
159
        var cp = p(child);
160
        if (cp.parent)
161
            cp.parent.removeChild(child);
162
        cp.parent = this;
163
        return child;
164
    };
165
}
166
```
167
Now, in order to access the private data of a `Node` object, you need to
168
have access to the unique `p` function that is being used here.  This is
169
already an improvement over the previous example, because it allows
170
restricted access by other `Node` instances, but can it help with the
171
`Node.prototype` problem too?
172

    
173
Yes it can!
174
```js
175
var p = require("private").makeAccessor();
176

    
177
function Node() {
178
    p(this).children = [];
179
}
180

    
181
var Np = Node.prototype;
182

    
183
Np.getParent = function() {
184
    return p(this).parent;
185
};
186

    
187
Np.appendChild = function(child) {
188
    p(this).children.push(child);
189
    var cp = p(child);
190
    if (cp.parent)
191
        cp.parent.removeChild(child);
192
    cp.parent = this;
193
    return child;
194
};
195
```
196
Because `p` is in scope not only within the `Node` constructor but also
197
within `Node` methods, we can finally avoid redefining methods every time
198
the `Node` constructor is called.
199

    
200
Now, you might be wondering how you can restrict access to `p` so that no
201
untrusted code is able to call it. The answer is to use your favorite
202
module pattern, be it CommonJS, AMD `define`, or even the old
203
Immediately-Invoked Function Expression:
204
```js
205
var Node = (function() {
206
    var p = require("private").makeAccessor();
207

    
208
    function Node() {
209
        p(this).children = [];
210
    }
211

    
212
    var Np = Node.prototype;
213

    
214
    Np.getParent = function() {
215
        return p(this).parent;
216
    };
217

    
218
    Np.appendChild = function(child) {
219
        p(this).children.push(child);
220
        var cp = p(child);
221
        if (cp.parent)
222
            cp.parent.removeChild(child);
223
        cp.parent = this;
224
        return child;
225
    };
226

    
227
    return Node;
228
}());
229

    
230
var parent = new Node;
231
var child = new Node;
232
parent.appendChild(child);
233
assert.strictEqual(child.getParent(), parent);
234
```
235
Because this version of `p` never leaks from the enclosing function scope,
236
only `Node` objects have access to it.
237

    
238
So, you see, the claim I made at the beginning of this README remains
239
true:
240

    
241
> In JavaScript, the only data that are truly private are local variables
242
> whose values do not *leak* from the scope in which they were defined.
243

    
244
It just so happens that closure privacy is sufficient to implement a
245
privacy model similar to that provided by other languages.
(2-2/4)