├── .gitignore ├── DECORATORS.md ├── FAQ.md ├── METHODS.md ├── OLD_README.md ├── README.md ├── STATIC.md └── spec ├── definitions.html ├── index.html ├── initialization.html ├── introduction.html ├── private-field-values.html └── references.html /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | _* 3 | 4 | -------------------------------------------------------------------------------- /DECORATORS.md: -------------------------------------------------------------------------------- 1 | The purpose of this document is to outline a possible path for further evolution of the private state proposal. The idea is to make sure that this proposal is consistent with the described evolution path, but to keep these additional features for follow-on proposals. This proposal does not change the semantics of any working code from the main proposal in this repository 2 | 3 | ## Strawman interaction between private fields and decorators 4 | 5 | You may want to have decorators over private fields, just like decorators on 6 | ordinary property declarations. The syntax could look like this: 7 | 8 | ```js 9 | class Foo { 10 | @decorator 11 | #bar = baz; 12 | } 13 | ``` 14 | 15 | Extrapolating from [the current decorator proposal](https://github.com/tc39/proposal-decorators/), the function `decorator` would be passed one descriptor and output an array of descriptors of entries in the class. 16 | 17 | The question for private state is, what do those descriptors look like for syntactic private field declarations? And, symmetrically, how can can other decorators create new private fields as part of their expansion? 18 | 19 | ### `PrivateFieldIdentifier` 20 | 21 | The proposal here is to make a new class, `PrivateFieldIdentifier`, which reifies a private state field across various instances. In the concept, `PrivateFieldIdentifier` is identical to [`WeakMap`](https://tc39.github.io/ecma262/#sec-weakmap-objects), differing critically in garbage collection: 22 | 23 | Semantically, the instances of objects in which `PrivateFieldIdentifier` has been added as a member "have a reference to" the `PrivateFieldIdentifier` object. That is, unlike with `WeakMaps`, if nobody explicitly references the `PrivateFieldIdentifier`, but the `PrivateFieldIdentifier` has an entry where an object is a key, and that object is still alive, then the value corresponding to the object key is still alive. Or, stated in the terms of the spec mechanics, `PrivateFieldIdentifier` has a \[\[PrivateID]], and adding an object to a `PrivateFieldIdentifier` adds an entry in that objects \[\[PrivateFields]] record mapping the \[\[PrivateID]] to the provided value. 24 | 25 | `PrivateFieldIdentifier.prototype` has three methods: `add(object, value)` (which throws if the field exists), `set(object, value)` (which throws if the field does not exist), and `get(object)` (which throws if the field does not exist). These correspond to the operations in private state of adding a field, getting a field and setting a field. The constructor returns a new PrivateFieldIdentifier, taking a single optional argument, like `Symbol`, which is just used for printing purposes. 26 | 27 | ### Decorator reification of private state 28 | 29 | With first-class `PrivateFieldIdentifier`, decorators on private state can be represented analogously to decorators on public property declarations. The above code sample may result in a descriptor such as the following being passed to the `decorator` function: 30 | 31 | ```js 32 | { 33 | type: 'privateField', 34 | name: '#bar', 35 | key: new PrivateFieldIdentifier('#bar'); 36 | initializer: () => baz, 37 | } 38 | ``` 39 | 40 | Decorators may accept these as arguments, or generate them as entries in the array returned from the decorator to add to the class. 41 | 42 | ### Polyfill and implementation notes 43 | 44 | Including `PrivateFieldIdentifier` as a built-in in the standard library actually doesn't add any expressive power at all. It can already be implemented with the proposal out for review using the super return trick. 45 | 46 | ```js 47 | class SuperClass { 48 | constructor(receiver) { return receiver; } 49 | } 50 | 51 | export class PrivateFieldIdentifier { 52 | // klasses have been appointed to lead OO design in the transition team 53 | #klass = class extends SuperClass { 54 | #member; 55 | static get(receiver) { 56 | return receiver.#member; 57 | } 58 | static set(receiver, value) { 59 | return receiver.#member = value; 60 | } 61 | }; 62 | get(receiver) { 63 | #klass.get(receiver); 64 | } 65 | set(receiver, object) { 66 | #klass.set(receiver, object); 67 | } 68 | add(reciever, object) { 69 | new #klass(receiver); 70 | this.set(receiver, object); 71 | } 72 | } 73 | ``` 74 | 75 | Some JavaScript implementations already have features which are analogous to `PrivateFieldIdentifier`. V8 has "private symbols" which are not passed to Proxies, don't go up prototype chains in their lookup, and which can be defined on any object (including a Proxy). For V8, PrivateFieldIdentifier can be easily implemented by simply giving it a private field which is a private symbol, where the `get`, `set` and `add` methods simply perform property access with this private symbol. 76 | 77 | ## 'Protected'-style state through decorators on private state 78 | 79 | One major feature request for the private state proposal is to ensure that there is a path to protected state or friends. Protected state is requested because there are times in evolving a program when some state may be better off having its access discouraged, but still available to some users, such as subclasses, or privileged classes within the same framework. 80 | 81 | ### Sidebar: protected state does not add any strong properties to JavaScript 82 | 83 | If you have a class with protected state, it is possible to read protected state out of instances without being a subclass. Let's say that protected members can be defined as `protected #foo;`, and the scope of `#foo` is both the class where it's defined as well as all subclasses. A 'hostile' subclass can provide a getter which can read that out of instances of the superclass. 84 | 85 | ```js 86 | class Superclass { 87 | protected #foo; 88 | constructor(foo) { #foo = foo; } 89 | } 90 | let x = new Superclass(1); 91 | 92 | class EvilSubclass extends Superclass { 93 | static getFoo(receiver) { return receiver.#foo; } 94 | } 95 | console.log(EvilSubclass.getFoo(x)); 96 | ``` 97 | 98 | So, since protected state doesn't actually enforce privacy, the main thing we are getting out of protected state is that *access is obscured*--you have to go through some steps (e.g., being in a subclass, or building that workaround) to get at the data. 99 | 100 | ### Putting state in an obscured location via decorators 101 | 102 | There are lots of ways that an 'escape hatch' for private state could be exposed via decorators. Below is one possible code sample: 103 | 104 | ```js 105 | class Example { 106 | @hidden 107 | #foo; 108 | constructor(foo) { #foo = foo; } 109 | } 110 | let x = new Example(1); 111 | console.log(Example.getHiddenFoo(x)); // => 1 112 | ``` 113 | 114 | This could be implemented by the following decorator: 115 | 116 | ```js 117 | function hidden(descriptor) { 118 | let getterDescriptor = { 119 | type: 'method', 120 | isStatic: true, 121 | key: 'getHidden' + descriptor.name[1].toUpperCase() + descriptor.name.slice(2), 122 | value(receiver) { 123 | return descriptor.key.get(receiver); 124 | } 125 | }; 126 | return [descriptor, getterDescriptor]; 127 | } 128 | ``` 129 | 130 | ### TypeScript-style escape hatch--just use square brackets 131 | 132 | In TypeScript, if something is marked `private`, you can get around that by using indexing with square brackets, rather than `.`. We could expose something similar using decorators. Example code: 133 | 134 | ```js 135 | class Example { 136 | @indexable 137 | #foo; 138 | constructor(foo) { #foo = foo; } 139 | } 140 | class Subclass extends Example { 141 | printFoo() { console.log(this['#foo']); } 142 | setFoo(value) { this['#foo'] = value; } 143 | } 144 | let x = new Subclass(1); 145 | x.printFoo(); // => 1 146 | x.setFoo(2); 147 | x.printFoo(); // => 2 148 | ``` 149 | 150 | The indexing is available to subclasses and outside of the class. Here's an implementation: 151 | 152 | ```js 153 | function indexable(descriptor) { 154 | let getterSetterDescriptor = { 155 | type: 'accessor', 156 | key: descriptor.name, 157 | get(receiver) { 158 | return descriptor.key.get(receiver); 159 | }, 160 | set(receiver, value) { 161 | return descriptor.key.set(receiver, value); 162 | } 163 | }; 164 | return [descriptor, getterSetterDescriptor]; 165 | } 166 | ``` 167 | 168 | 169 | ### Protected-style inheritance of private state via decorators 170 | 171 | But what if you want to do more, and really get something that looks like protected state, with its inheritance chain and everything? Here's an example of usage: 172 | 173 | ```js 174 | class Example { 175 | @protected 176 | #foo; 177 | constructor(foo) { #foo = foo; } 178 | } 179 | class Subclass extends Example { 180 | printFoo() { console.log(this.protected.foo); } 181 | setFoo(value) { this.protected.foo = value; } 182 | } 183 | let x = new Subclass(1); 184 | x.printFoo(); // => 1 185 | x.setFoo(2); 186 | x.printFoo(); // => 2 187 | ``` 188 | 189 | How could that work? Well, the catch here is that `this.protected` is just as available outside of the class (but, this doesn't have any real implications for privacy, as discussed in the sidebar at the beginning of the document). `protected` could be implemented as follows (untested, and a strawman, so slow in many ways!): 190 | 191 | ```js 192 | // Maps names to an Array of PrivateFieldIdentifiers with that name 193 | let protectedFields = new Map(); 194 | 195 | function findProtected(object, property) { 196 | for (let key in protectedFields.get(property)) { 197 | try { 198 | key.get(object); 199 | return key; 200 | } catch (e) { 201 | continue; 202 | } 203 | } 204 | throw new TypeError(); 205 | } 206 | 207 | export function protected(descriptor) { 208 | let name = descriptor.name.slice(1); 209 | if (!protectedFields.has(name)) { protectedFields.set(name, []) } 210 | protectedFields.get(name).push(descriptor.key); 211 | return [descriptor]; 212 | } 213 | 214 | Object.defineProperty(Object.prototype, 'protected', { get() { 215 | let object = this; 216 | return new Proxy({}, { 217 | get(target, property, receiver) { 218 | return findProtected(object, property).get(object); 219 | } 220 | set(target, property, value, receiver) { 221 | return findProtected(object, property).set(object, value); 222 | } 223 | }); 224 | } }); 225 | ``` 226 | 227 | It would be a little more complicated for methods (`getProtected` would have to return bound methods based on the underlying one, so that we get the receiver right) but should be possible in a similar way. 228 | 229 | I suspect that a decorator like `@hidden` or `@indexed` is what most use cases would need, rather than this, however. 230 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | ## Why aren't declarations `private x`? 2 | 3 | This sort of declaration is what other languages use (notably Java), and implies that access would be done with `this.x`. Assuming that isn't the case (see below), in JavaScript this would silently create or access a public field, rather than throwing an error. This is a major potential source of bugs or invisibly making public fields which were intended to be private. 4 | 5 | ## Why isn't access `this.x`? 6 | 7 | Having a private field named `x` must not prevent there from being a public field named `x`, so accessing a private field can't just be a normal lookup. 8 | 9 | Less significantly, this would also break an invariant of current syntax: `this.x` and `this['x']` are currently always semantically identical. 10 | 11 | ### Why does this proposal allow a class to have a private field `#x` *and* a public field `x`? 12 | 13 | 1. If private fields conflicted with public fields, it would break encapsulation; see below. 14 | 15 | 1. One of the more important properties of private fields is that subclasses don't need to know about them. We'd like to allow a subclass to define a property named `x` even if its superclass has a private field of that name. 16 | 17 | This is something other languages with private fields generally allow. E.g., the following is perfectly legal Java: 18 | 19 | ```java 20 | class Base { 21 | private int x = 0; 22 | } 23 | 24 | class Derived extends Base { 25 | public int x = 0; 26 | } 27 | ``` 28 | 29 | ### Why not do a runtime check on the type of the receiver to determine whether to access the private or public field named `x`? 30 | 31 | Property access semantics are already complicated, and we don't want to slow down every property access just to add this feature. 32 | 33 | It also would allow methods of the class to be tricked into operating on public fields of non-instances as if they were private fields of instances. See [this comment](https://github.com/tc39/proposal-private-fields/issues/14#issuecomment-153050837) for an example. 34 | 35 | ### Why not just always have `obj.x` refer to a private field inside of a class which declares a private field `x`? 36 | 37 | Class methods often manipulate objects which are not instances of the class. It would be surprising if the code `obj.x` suddenly stopped referring to public field `x` of `obj`, when `obj` is not expected to be an instance of the class, just because that code happened to occur somewhere within a class which declares a private field named `x`, possibly deep within said class. 38 | 39 | This is only an issue in JavaScript because of its lack of static types. Statically typed languages use type declarations to distinguish the external-public/internal-private cases without the need of a sigil. But a dynamically typed language doesn't have enough static information to differentiate those cases. 40 | 41 | ### Why not give the `this` keyword special semantics? 42 | 43 | `this` is already a source of enough confusion in JS; we'd prefer not to make it worse. Also, it's a major refactoring hazard: it would be surprising if `this.x` had different semantics from `const thiz = this; thiz.x`. 44 | 45 | This also wouldn't allow accessing fields of objects other than `this`. 46 | 47 | ### Why not only allow accessing fields of `this`, e.g. by just having a bare `x` refer to the private field `x`? 48 | 49 | It is a goal of this proposal to allow accessing private fields of other instances of the same class (see below), which requires some syntax. Also, using bare identifiers to refer to properties is not the usual JS way (with the exception of `with`, which is generally considered to have been a mistake). 50 | 51 | ### Why doesn't `this['#x']` access the private field named `#x`, given that `this.#x` does? 52 | 53 | 1. This would complicate property access semantics. 54 | 55 | 1. Dynamic access to private fields is contrary to the notion of 'private'. E.g. this is concerning: 56 | 57 | ```js 58 | class Dict extends null { 59 | #data = something_secret; 60 | add(key, value) { 61 | this[key] = value; 62 | } 63 | get(key) { 64 | return this[key]; 65 | } 66 | } 67 | 68 | (new Dict).get('#data'); // returns something_secret 69 | ``` 70 | 71 | #### But doesn't giving `this.#x` and `this['#x']` different semantics break an invariant of current syntax? 72 | 73 | Not exactly, but it is a concern. `this.#x` has never previously been legal syntax, so from one point of view there can be no invariant regarding it. 74 | 75 | On the other hand, it might be surprising that they differ, and this is a downside of the current proposal. 76 | 77 | ## Why not have access be `this#x`, without the dot? 78 | 79 | It's an ASI hazard [for the shorthand syntax of `#x`](https://github.com/tc39/proposal-private-fields/issues/39#issuecomment-237121552) and for declarations: 80 | 81 | ```js 82 | class Person { 83 | properties = {} 84 | #id 85 | } 86 | ``` 87 | would actually parse as 88 | 89 | ```js 90 | class Person { 91 | properties = {}#id 92 | } 93 | ``` 94 | 95 | ## Why does this proposal allow accessing private fields of other instances of the same class? Don't other languages normally forbid that? 96 | 97 | It's very useful: see e.g. the `equals` method in the `Point` example in the readme. And in fact, [other languages](https://github.com/tc39/proposal-private-fields/issues/90#issuecomment-307201358) allow it for the same reason; e.g. the following is perfectly legal Java: 98 | 99 | ```java 100 | class Point { 101 | private int x = 0; 102 | private int y = 0; 103 | public boolean equals(Point p) { return this.x == p.x && this.y == p.y; } 104 | } 105 | ``` 106 | ## Why was the sigil `#` chosen, among all the Unicode code points? 107 | 108 | No one came out and said, `#` is the most beautiful, intuitive thing to indicate private state. Instead, it was more of a process of elimination: 109 | - `@` was the initial favorite, but it was taken by decorators. TC39 considered swapping decorators and private state sigils, but the committee decided to defer to the existing usage of transpiler users. 110 | - `_` would cause compatibility issues with existing JavaScript code, which has allowed `_` at the start of an identifier or (public) property name for a long time. 111 | - Other characters which could be used as infix operators, but not prefix operators, are hypothetically possible, such as `%`, `^`, `&`, `?`, given that our syntax is a bit unique--`x.%y` is not valid currently, so there would be no ambiguity. However, the shorthand would lead to ASI issues, e.g., the following would look like using the infix operator: 112 | ```js 113 | class Foo { 114 | %x; 115 | method() { 116 | calculate().my().value() 117 | %x.print() 118 | } 119 | } 120 | ``` 121 | Here, the user likely intended to call the `print` method on `this.%x`, but instead, the mod operator will be used! 122 | - Other Unicode code points which are not in ASCII or IDStart could be used, but these might be hard to input for many users; they are not found on common keyboards. 123 | 124 | In the end, the only other options are longer sequences of punctuation, which seems suboptimal compared to a single character. 125 | 126 | ## Why doesn't this proposal allow some mechanism for reflecting on / accessing private fields from outside the class which declares them (e.g. for testing)? Don't other languages normally allow that? 127 | 128 | Doing so would violate encapsulation (see below). That other languages allow it isn't sufficient reason on its own, especially since in some of them (e.g. C++) this is accomplished by modifying memory directly and is not necessarily a goal. 129 | 130 | ## What do you mean by "encapsulation" / "hard private"? 131 | 132 | It means that private fields are *purely* internal: no JS code outside of a class can detect or affect the existence, name, or value of any private field of instances of said class without directly inspecting the class's source, unless the class chooses to reveal them. (This includes subclasses and superclasses.) 133 | 134 | This means that reflection methods like [getOwnPropertySymbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols) must not reveal private fields. 135 | 136 | This also means that if a class has a private field named `x`, code outside the class which does `obj.x` on an instance `obj` of the class should access the public field `x` just as it would in the absence of the private field. It must *not* access the private field or throw an error. Note that this doesn't come up as much in languages like Java, which can be type-checked at compile time and do not allow dynamic access to fields by name except via reflection APIs. 137 | 138 | ## Why is encapsulation a goal of this proposal? 139 | 140 | 1. Library authors have found that their users will start to depend on any exposed part of their interface, even undocumented parts. They do not generally consider themselves free to break their user's pages and applications just because those users were depending upon some part of the library's interface which the library author did not intend them to depend upon. As a consequence, they would like to have hard private state to be able to hide implementation details in a more complete way. 141 | 142 | 1. While it's already possible to *model* true encapsulation using either per-instance closures or WeakMaps (see below), both approaches are unergonomic to use with classes and have memory usage semantics which may be surprising. Furthermore, per-instance closures don't allow instances of the same class to access each other's private fields (see [above](#why-does-this-proposal-allow-accessing-private-fields-of-other-instances-of-the-same-class-dont-other-languages-normally-forbid-that)), and with WeakMaps there's a non-obvious risk of exposing private data. WeakMaps are also likely to be slower than private fields could be. 143 | 144 | 1. "Hidden" but not encapsulated properties are likewise already possible through the use of Symbols as property names (see below). 145 | 146 | This proposal is currently moving forward with hard-private fields, letting [decorators](https://github.com/tc39/proposal-private-fields/blob/master/DECORATORS.md) or other mechanisms provide classes an opt-in escape hatch. We intend to gather feedback during this process to help determine whether this is the correct semantics. 147 | 148 | See [this issue](https://github.com/tc39/proposal-private-fields/issues/33) for more. 149 | 150 | ### How can you model encapsulation using WeakMaps? 151 | 152 | ```js 153 | const Person = (function(){ 154 | const privates = new WeakMap; 155 | let ids = 0; 156 | return class Person { 157 | constructor(name) { 158 | this.name = name; 159 | privates.set(this, {id: ids++}); 160 | } 161 | equals(otherPerson) { 162 | return privates.get(this).id === privates.get(otherPerson).id; 163 | } 164 | } 165 | })(); 166 | let alice = new Person('Alice'); 167 | let bob = new Person('Bob'); 168 | alice.equals(bob); // false 169 | ``` 170 | 171 | There's a potential pitfall with this approach, though. Suppose we want to add a custom callback to construct greetings: 172 | 173 | ```js 174 | const Person = (function(){ 175 | const privates = new WeakMap; 176 | let ids = 0; 177 | return class Person { 178 | constructor(name, makeGreeting) { 179 | this.name = name; 180 | privates.set(this, {id: ids++, makeGreeting}); 181 | } 182 | equals(otherPerson) { 183 | return privates.get(this).id === privates.get(otherPerson).id; 184 | } 185 | greet(otherPerson) { 186 | return privates.get(this).makeGreeting(otherPerson.name); 187 | } 188 | } 189 | })(); 190 | let alice = new Person('Alice', name => `Hello, ${name}!`); 191 | let bob = new Person('Bob', name => `Hi, ${name}.`); 192 | alice.equals(bob); // false 193 | alice.greet(bob); // === 'Hello, Bob!' 194 | ``` 195 | 196 | At first glance this appears fine, but: 197 | 198 | ```js 199 | let mallory = new Person('Mallory', function(name) {this.id = 0; return `o/ ${name}`;}); 200 | mallory.greet(bob); // === 'o/ Bob' 201 | mallory.equals(alice); // true. Oops! 202 | ``` 203 | 204 | ### How can you provide hidden but not encapsulated properties using Symbols? 205 | 206 | ```js 207 | const Person = (function(){ 208 | const _id = Symbol('id'); 209 | let ids = 0; 210 | return class Person { 211 | constructor(name) { 212 | this.name = name; 213 | this[_id] = ids++; 214 | } 215 | equals(otherPerson) { 216 | return this[_id] === otherPerson[_id]; 217 | } 218 | } 219 | })(); 220 | let alice = new Person('Alice'); 221 | let bob = new Person('Bob'); 222 | alice.equals(bob); // false 223 | 224 | alice[Object.getOwnPropertySymbols(alice)[0]]; // == 0, which is alice's id. 225 | ``` 226 | -------------------------------------------------------------------------------- /METHODS.md: -------------------------------------------------------------------------------- 1 | The purpose of this document is to outline a possible path for further evolution of the private state proposal. The idea is to make sure that this proposal is consistent with the described evolution path, but to keep these additional features for follow-on proposals. This proposal does not change the semantics of any working code from the main proposal in this repository. 2 | 3 | ## Strawman private methods outline 4 | 5 | Private methods are a natural next step for privacy in ECMAScript classes. One may imagine private methods looking like this: 6 | 7 | ```js 8 | class Foo { 9 | #a; 10 | #b; 11 | #sum() { return #a + #b; } 12 | printSum() { console.log(#sum()); } 13 | constructor(a, b) { #a = a; #b = b; } 14 | } 15 | let f = new Foo(1, 2); 16 | f.printSum(); // prints 3 17 | ``` 18 | 19 | I believe we can make this Just Work(TM). 20 | 21 | The key is how to allow private state fields and private methods work side-by-side. Syntax like `#sum()` would seem ambiguous: is this looking up the `#sum` private field and then calling it, or is it calling a method (on the prototype???) which is private called `#sum`. 22 | 23 | This document describes two alternative ways of defining the semantics to describe how this would be specified, with minimal observable differences. 24 | 25 | ### Option 1: Private methods are just immutable own private fields which are functions 26 | 27 | Imagine that the above method definition is simply syntactic sugar for 28 | 29 | ```js 30 | class Foo { 31 | #a; #b; 32 | #sum = function() { return #a + #b; }; 33 | } 34 | ``` 35 | 36 | This would work out for the rest of the code sample just fine: The receiver for a call like `#sum()` would be the local lexical `this`, just like in a normal method call. 37 | 38 | However, from an implementation efficiency point of view, there is a problem: the addition of an own, mutable private field means that implementations may have to actually allocate storage space for the property. 39 | 40 | In the spirit of private state being generally stricter, and to reduce that potential source of slowdown, this proposal would make the property immutable. Then, implementations can deterministically optimize away its storage to make that simply a reference to a particular function, based on which class it is contained in. 41 | 42 | Looking up which function is relevant would be part of the work implementations already have to do for scoping, wherein, for example, implementations also have to determine which class the private field corresponds to. If [private state is not visible within eval](https://github.com/tc39/proposal-private-fields/issues/47), the implementation is simpler, but it is possible to do either way. 43 | 44 | ### Option 2: Private method references are a different type, resolved lexically 45 | 46 | In this option, there would be an additional reference type which is introduced to describe private methods. In the `GetValue` internal algorithm, we have the following new steps: 47 | 48 | 1. If IsPrivateReference(_V_), then 49 | 1. Let _privateMap_ be ? ResolveBinding(GetReferencedName(_V_)). 50 | 1. Assert: _privateMap_ is a WeakMap object. 51 | 1. If WeakMapHas(_privateMap_, _base_) is *false*, throw a *TypeError* exception. 52 | 1. Return ! WeakMapGet(_privateMap_, _base_). 53 | 54 | Instead of assuming the value is a `WeakMap`, we could make two options: either it is a WeakMap, or it is a method. If it's a method, then treat this like a method invocation. 55 | 56 | Again, as with the previous semantics, this would be easier to implement without direct `eval` being able to see private data, but it is possible either way. 57 | -------------------------------------------------------------------------------- /OLD_README.md: -------------------------------------------------------------------------------- 1 | ## Current status 2 | 3 | This proposal has been merged into the [class fields](https://github.com/tc39/proposal-class-fields) proposal. Please see that repository for current status. The rest of this repository is left up as a historical archive. 4 | 5 | Note, this historical version of the specification includes the "private field shorthand" feature, where `#x` is shorthand for `this.#x`. That feature has been removed from the [current class fields proposal](https://github.com/tc39/proposal-class-fields), set to be proposed as a possible follow-on. To minimize confusion, shorthand has been removed from this explainer, though it is still present in supporting documents and the specification text. 6 | 7 | ## ECMAScript Private Fields 8 | 9 | ### A Brief Introduction 10 | 11 | Private field names are represented as an identifier prefixed with the `#` character. Private field definitions create immutable bindings which are lexically confined to their containing class body and are not reified. In the following example, `#x` and `#y` identify private fields whose type is guaranteed to be **Number**. 12 | 13 | ```js 14 | class Point { 15 | 16 | #x; 17 | #y; 18 | 19 | constructor(x = 0, y = 0) { 20 | this.#x = +x; 21 | this.#y = +y; 22 | } 23 | 24 | get x() { return this.#x } 25 | set x(value) { this.#x = +value } 26 | 27 | get y() { return this.#y } 28 | set y(value) { this.#y = +value } 29 | 30 | equals(p) { return this.#x === p.#x && this.#y === p.#y } 31 | 32 | toString() { return `Point<${ this.#x },${ this.#y }>` } 33 | 34 | } 35 | ``` 36 | 37 | Private fields may also have an initializer expression. Private field initializers are evaluated when the constructor's **this** value is initialized. 38 | 39 | ```js 40 | class Point { 41 | #x = 0; 42 | #y = 0; 43 | 44 | constructor() { 45 | this.#x; // 0 46 | this.#y; // 0 47 | } 48 | } 49 | ``` 50 | 51 | ### Private State Object Model 52 | 53 | #### Private Field Identiers 54 | 55 | Each field definition effectively creates a unique internal slot identifer. In the specification mechanics, this is based on a Private Field Identifier value, not accessible to user code, which is associated through an internal slot in the object with a value. The system can be thought of as equivalent to WeakMaps, with the only difference being the (implied) garbage collection semantics--the value for the private field is held alive by the object, even if nothing else points to the Private Field Identifier. 56 | 57 | #### Constructors and Field Initialization 58 | 59 | Each ECMAScript function object has an internal slot named `[[PrivateFieldDefinitions]]` which contains a possibly-empty list of Private Field Identifiers and initializer expressions. When a class definition is evaluated, the `[[PrivateFieldDefinitions]]` list of the newly created constructor is populated with a Private Field Identifier for each private name definition within the class body. The constructor adds entries to each object's internal slots, associating the Private Field Identifier to the appropriate value, in this list at the following times: 60 | 61 | 1. For a base class, after the new object is allocated. 62 | 1. For a derived class, immediately after the super call returns. 63 | 64 | ### Syntax 65 | 66 | The lexical grammar is extended with an additional token: 67 | 68 | ``` 69 | PrivateName :: 70 | `#` IdentifierName 71 | ``` 72 | 73 | Private field definitions are allowed within class bodies: 74 | 75 | ``` 76 | PrivateFieldDefinition[Yield] : 77 | PrivateName Initializer[In, ?Yield]? `;` 78 | 79 | ClassElement[Yield] : 80 | ... 81 | PrivateFieldDefinition[?Yield] 82 | ``` 83 | 84 | Each private field definition creates a lexical binding from a private name to a private field WeakMap. 85 | 86 | If an initializer is provided, it is run immediately before the **this** value has been bound to the new object. In derived classes, this will occur after the super call is evaluated. 87 | 88 | It is a syntax error if there are any duplicate private field definitions. 89 | 90 | Member expressions are extended to allow private references: 91 | 92 | ``` 93 | MemberExpression[Yield] : 94 | ... 95 | MemberExpression[?Yield] `.` PrivateName 96 | ``` 97 | 98 | A concise member expression syntax also exists, where `#x` is shorthand for `this.#x`. 99 | 100 | When such a reference is evaluated, the private name is lexically resolved to a private field WeakMap. The WeakMap is then used to access the field data associated with the object. 101 | 102 | If the WeakMap does not contain an entry for the object a TypeError is thrown. 103 | 104 | It is an early error if a member expression contains a private name which cannot be statically resolved. 105 | 106 | ### Frequently Asked Questions ### 107 | 108 | **Q**: Why do we have to use a special character in front of the identifier? 109 | 110 | **A**: In short, this seems to be the only way that the system can reliably enforce who has access to the private state in a world with fully dynamic type checking and eval. See [this answer](https://github.com/tc39/proposal-private-fields/issues/14#issuecomment-153050837) for a more detailed explanation of options. 111 | 112 | **Q**: Why not use a private version of symbols? 113 | 114 | **A**: Private symbols were found to not interact well with membranes used to support certain security paradigms. See [this comment](https://github.com/zenparsing/es-abstract-refs/issues/11#issuecomment-65723350) for details. 115 | 116 | **Q**: Why aren't private methods in this proposal? 117 | 118 | **A**: This proposal attempts to be minimal, but compatible with a follow-on private methods proposal. See [METHODS.md](https://github.com/tc39/proposal-private-fields/blob/master/METHODS.md) for details. 119 | 120 | **Q**: How does private state interact with decorators? 121 | 122 | **A**: Private field declarations should be analogous to class property declarations in how they work with decorators. See [DECORATORS.md](https://github.com/tc39/proposal-private-fields/blob/master/DECORATORS.md) for a strawman. 123 | 124 | **Q**: Should classes be able to have private fields? 125 | 126 | **A**: Also a good possible follow-on proposal;, see [STATIC.md](https://github.com/tc39/proposal-private-fields/blob/master/STATIC.md) for details. 127 | 128 | **Q**: Can we use `@` rather than `#` for the sigil, like Ruby? 129 | 130 | **A**: TC39 considered this question in the September 2016 TC39 meeting and decided to stick with `@` being proposed for decorators for now. One factor in the decision was the ecosystem of users in transpilation who are using `@` for decorators already. 131 | 132 | **Q**: Can we reconsider, in the syntax, the decision to do... 133 | 134 | **A**: Yes, it's not too late. Two active discussions on syntax are [whether the declaration should have the word `private` in it](https://github.com/tc39/proposal-private-fields/issues/53) and [what the token should be for initializing a field](https://github.com/tc39/proposal-class-public-fields/issues/33). However, there are other decisions, such as the need for a sigil, and the inability to use `@` for the sigil, that are set for particular strong reasons described above. 135 | 136 | If you have an alternative syntax you'd like to suggest, please read [the FAQ](https://github.com/tc39/proposal-private-fields/blob/master/FAQ.md) and understand the constraints. 137 | 138 | **Q**: I have another question about the design of the proposal. 139 | 140 | **A**: Check [the FAQ](https://github.com/tc39/proposal-private-fields/blob/master/FAQ.md). 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## This proposal has been merged into the [class fields](https://github.com/tc39/proposal-class-fields) proposal. 2 | 3 | Please see that repository for current status. The rest of this repository is left up as a historical archive. The old readme is available [here](OLD_README.md), but will not be kept up to date. Similarly, while the FAQ in this repository will continue to exist as is, it will not be updated; its canonical location incluing any updates is in the [class fields repository](https://github.com/tc39/proposal-class-fields/blob/master/PRIVATE_SYNTAX_FAQ.md). -------------------------------------------------------------------------------- /STATIC.md: -------------------------------------------------------------------------------- 1 | The purpose of this document is to outline a possible path for further evolution of the private state proposal. The idea is to make sure that this proposal is consistent with the described evolution path, but to keep these additional features for follow-on proposals. This proposal does not change the semantics of any working code from the main proposal in this repository. 2 | 3 | ## Static private field outline 4 | 5 | It should be possible to add private static fields, as well as methods, per 6 | [this explainer](https://github.com/tc39/proposal-private-fields/blob/master/METHODS.md). The syntax could look like this: 7 | 8 | ```js 9 | class MyClass { 10 | static #foo = 1; 11 | static #bar() { ... } 12 | }; 13 | ``` 14 | 15 | Static private methods and fields would be visible only within the body of the class. They have one value, only for the class 16 | they are defined in, not for subclasses as well. The semantics could be defined two ways: 17 | - These are just lexically scoped variables which have special syntax to make them scoped to the class 18 | - The class actually gets these as private fields and methods, with those same semantics as for instance private fields 19 | 20 | I don't think there are observable differences to these semantics. 21 | -------------------------------------------------------------------------------- /spec/definitions.html: -------------------------------------------------------------------------------- 1 |
With parameter _homeObject_.
139 |With parameter _functionObject_.
161 | 162 |With parameter _className_.
191 |constructor(... args){ super (...args);}
229 | using the syntactic grammar with the goal symbol |MethodDefinition|.
230 | 1. Else,
231 | 1. Let _constructor_ be the result of parsing the source text
232 | constructor( ){ }
233 | using the syntactic grammar with the goal symbol |MethodDefinition|.
234 | 1. Set the running execution context's LexicalEnvironment to _classScope_.
235 | 1. Set the running execution context's PrivateFieldEnvironment to _classPrivateEnvironment_.
236 | 1. Let _constructorInfo_ be the result of performing DefineMethod for _constructor_ with arguments _proto_ and _constructorParent_ as the optional _functionPrototype_ argument.
237 | 1. Assert: _constructorInfo_ is not an abrupt completion.
238 | 1. Let _F_ be _constructorInfo_.[[closure]].
239 | 1. If |ClassHeritage_opt| is present, set _F_'s [[ConstructorKind]] internal slot to `"derived"`.
240 | 1. Perform MakeConstructor(_F_, *false*, _proto_).
241 | 1. Perform MakeClassConstructor(_F_).
242 | 1. Perform CreateMethodProperty(_proto_, `"constructor"`, _F_).
243 | 1. If |ClassBody_opt| is not present, let _methods_ be a new empty List.
244 | 1. Else, let _methods_ be NonConstructorMethodDefinitions of |ClassBody|.
245 | 1. For each |ClassElement| _m_ in order from _methods_,
246 | 1. If IsStatic of _m_ is *false*, then
247 | 1. Let _status_ be the result of performing PropertyDefinitionEvaluation for _m_ with arguments _proto_ and *false*.
248 | 1. Else,
249 | 1. Let _status_ be the result of performing PropertyDefinitionEvaluation for _m_ with arguments _F_ and *false*.
250 | 1. If _status_ is an abrupt completion, then
251 | 1. Set the running execution context's PrivateFieldEnvironment to _outerPrivateEnvironment_.
252 | 1. Set the running execution context's LexicalEnvironment to _lex_.
253 | 1. Return Completion(_status_).
254 | 1. If |ClassBody_opt| is not present, let _fieldDefinitions_ be a new empty List.
255 | 1. Else, let _fieldDefinitions_ be PrivateFieldDefinitions of |ClassBody|.
256 | 1. Let _privateFields_ be a new empty List.
257 | 1. For each |ClassElement| _f_ in order from _fieldDefinitions_,
258 | 1. Let _fieldRecord_ be the result of performing PrivateFieldDefinitionEvaluation for _f_ with argument _proto_.
259 | 1. Append _fieldRecord_ to _privateFields_.
260 |
261 | 1. Set the value of _F_'s [[PrivateFieldDefinitions]] internal slot to _privateFields_.
262 | 1. Set the running execution context's PrivateFieldEnvironment to _outerPrivateEnvironment_.
263 | 1. Set the running execution context's LexicalEnvironment to _lex_.
264 | 1. If _className_ is not *undefined*, then
265 | 1. Perform _classScopeEnvRec_.InitializeBinding(_className_, _F_).
266 | 1. Return _F_.
267 | 4 | title: Private Fields 5 | status: proposal 6 | stage: 1 7 | location: https://github.com/zenparsing/es-private-fields 8 | copyright: false 9 | contributors: Kevin Smith 10 |11 | 12 | 13 | 67 |
The [[Construct]] internal method for an ECMAScript Function object _F_ is called with parameters _argumentsList_ and _newTarget_. _argumentsList_ is a possibly empty List of ECMAScript language values. The following steps are taken:
24 |This algorithm has been modified to inline the abstract operation GetSuperConstructor.
60 |This proposal adds syntactic support for per-instance private object state by means of private field declarations within class bodies.
2 | 3 |See the proposal repository for background material and discussion.
4 | 5 |
7 | class Point {
8 | #x;
9 | #y;
10 |
11 | constructor(x = 0, y = 0) {
12 | #x = +x;
13 | #y = +y;
14 | }
15 |
16 | get x() { return #x }
17 | set x(value) { #x = +value }
18 |
19 | get y() { return #y }
20 | set y(value) { #y = +value }
21 |
22 | equals(p) { return #x === p.#x && #y === p.#y }
23 |
24 | toString() { return `Point<${ #x },${ #y }>` }
25 | }
26 |
27 | The Private Field Identifier specification type is used to describe a globally unique identifier which represents a private field name. A private field identifier may be installed on any ECMAScript object with the PrivateFieldAdd internal algorithm, and then read or written using PrivateFieldGet and PrivateFieldSet.
2 | 3 |All ECMAScript objects have a new additional internal slot, [[PrivateFieldValues]], which is a List of Records of the form { [[PrivateFieldIdentifer]]: Private Field Identifier, [[PrivateFieldValue]]: ECMAScript value }. This List represents the values of the private fields for the object. All objects, including Proxies and all host environment-provided objects, have this internal slot, but primitives such as Numbers do not.
4 | 5 |7 | Private fields are designed to have semantics analogous to WeakMaps. However, the implied garbage collection semantics are weaker: If all the references to a WeakMap are inaccessible, but there is still a reference to a key which was in the WeakMap, one would expect the value to be eventually collected. However, PrivateFieldIdentifiers specifically do not have this connotation: because the reference from the Identifier to the Value is in a Record which the Object points to, the value would not be collected, even if nothing else points to the identifier. 8 |
9 |10 | Private Field Identifiers are a specification type here, not directly observable to ECMAScript code. However, in a decorator integration strawman, an object wrapping Private Field Identifiers would be exposed to allow greater metaprogramming. 11 |
12 |The abstract operation ObjectCreate with argument _proto_ (an object or null) is used to specify the runtime creation of new ordinary objects. The optional argument _internalSlotsList_ is a List of the names of additional internal slots that must be defined as part of the object. If the list is not provided, a new empty List is used. This abstract operation performs the following steps:
21 |These static rules have been modified to produce an early error if the `delete` operator is applied to a private reference.
42 |It is a Syntax Error if the derived |UnaryExpression| is
50 |
51 |
53 | and |CoverParenthesizedExpressionAndArrowParameterList| ultimately derives a phrase that, if used in place of |UnaryExpression|, would produce a Syntax Error according to these rules. This rule is recursively applied.
The last rule means that expressions such as `delete (((foo)))` produce early errors because of recursive application of the first rule.
58 |When a `delete` operator occurs within strict mode code, a *SyntaxError* exception is thrown if its |UnaryExpression| is a direct reference to a variable, function argument, or function name. In addition, if a `delete` operator occurs within strict mode code and the property to be deleted has the attribute { [[Configurable]]: *false* }, a *TypeError* exception is thrown.
85 |The Reference type is used to explain the behaviour of such operators as `delete`, `typeof`, the assignment operators, the `super` keyword and other language features. For example, the left-hand operand of an assignment is expected to produce a reference.
93 |A Reference is a resolved name or property binding. A Reference consists of four components, the base value, the referenced name, the Boolean valued strict reference flag, and the Boolean valued private reference flag. The base value is either *undefined*, an Object, a Boolean, a String, a Symbol, a Number, or an Environment Record (
A Super Reference is a Reference that is used to represent a name binding that was expressed using the super keyword. A Super Reference has an additional _thisValue_ component and its _base_ value will never be an Environment Record.
96 |The following abstract operations are used in this specification to access the components of references:
97 |The following abstract operations are used in this specification to operate on references:
124 | 125 | 126 |The object that may be created in step 5.a.ii is not accessible outside of the above abstract operation and the ordinary object [[Get]] internal method. An implementation might choose to avoid the actual creation of the object.
148 |The object that may be created in step 6.a.ii is not accessible outside of the above algorithm and the ordinary object [[Set]] internal method. An implementation might choose to avoid the actual creation of that object.
182 |212 | Component 213 | | 214 |215 | Purpose 216 | | 217 |
---|---|
220 | LexicalEnvironment 221 | | 222 |223 | Identifies the Lexical Environment used to resolve identifier references made by code within this execution context. 224 | | 225 |
228 | VariableEnvironment 229 | | 230 |231 | Identifies the Lexical Environment whose EnvironmentRecord holds bindings created by |VariableStatement|s within this execution context. 232 | | 233 |
236 | PrivateFieldEnvironment 237 | | 238 |239 | Identifies the Lexical Environment whose EnvironmentRecord holds internal private field identifiers created by |PrivateFieldDefinition|s. 240 | | 241 |