├── .gitignore ├── README.md ├── assets └── membrane-shadow-target.svg ├── babel-plugins ├── README.md ├── package-lock.json ├── package.json ├── src │ └── underscore-to-private.js └── test │ └── underscore-to-private.js ├── membranes.md ├── spec-changes.md └── symbols-or-fields.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ECMAScript Proposal: Private Symbols 2 | 3 | ## Overview 4 | 5 | This proposal allows the user to create **private symbols**. 6 | 7 | ```js 8 | const privateSym = Symbol.private(); 9 | ``` 10 | 11 | A **private symbol** is semantically identical to a regular symbol, with the following exceptions: 12 | 13 | - Private symbols are not exposed by `Object.getOwnPropertySymbols`. 14 | - Private symbols are not copied by `Object.assign` or object spread. 15 | - Private symbol-keyed properties are not affected by `Object.freeze` and `Object.seal`. 16 | - Private symbols are not exposed to proxy handlers. 17 | 18 | See [Semantics](#semantics) for more details. 19 | 20 | Like regular symbols, private symbols may be created with a descriptive string: 21 | 22 | ```js 23 | const privateSym = Symbol.private('My private symbol'); 24 | ``` 25 | 26 | The Symbol prototype has a `private` getter function which can be used to determine whether the symbol is private: 27 | 28 | ```js 29 | if (someSymbol.private) { 30 | console.log('This is a private symbol'); 31 | } else { 32 | console.log('This is not a private symbol'); 33 | } 34 | ``` 35 | 36 | ## Goals 37 | 38 | This proposal is intended to provide a missing capability to JavaScript: the ability to add properties to an object that are inaccessible without a unique and unforgable key. Moreover, this proposal is intended to be *minimal*, completely *generalized* and strictly *orthogonal*. 39 | 40 | - It is *minimal* because it introduces the minimum amount of new concepts to the language. 41 | - It is *generalized* because it is not tied to any particular type of object or syntactic structure. 42 | - It is *orthogonal* because it does not attempt to solve any problem other than property encapsulation. 43 | 44 | The proposal **does not** attempt to provide solutions for: 45 | 46 | - Syntactic sugar for symbol-keyed property definition and access. 47 | - Secure branding mechanisms. 48 | - Static shape guarantees. 49 | 50 | ## Some Questions and Answers 51 | 52 | ### __*Do objects inherit private symbols from their prototypes?*__ 53 | 54 | Yes. 55 | 56 | Private symbols work just like regular symbols when it comes to prototype lookup. There is nothing new to learn. This means that "private methods" on classes just work, without any additional semantics: 57 | 58 | ```js 59 | const method = Symbol.private(); 60 | 61 | class C { 62 | [method]() { 63 | console.log('This is a private method, installed on the prototype'); 64 | } 65 | } 66 | 67 | let c = new C(); 68 | c[method](); 69 | ``` 70 | 71 | ### __*Why don't `Object.assign` and the spread operator copy private symbols?*__ 72 | 73 | It follows from the definition of `Object.assign` and the spread operator. 74 | 75 | They both work by obtaining a list of property keys from the source object, using the `[[OwnPropertyKeys]]` internal method. Since we do not want to allow private symbols to leak, we restrict the definition of `[[OwnPropertyKeys]]` such that it is not allowed to return private symbols. 76 | 77 | ### __*Why don't `Object.freeze` and `Object.seal` affect private symbol-keyed properties?*__ 78 | 79 | When an object is frozen or sealed, the object is first marked as non-extensible (meaning new properties cannot be added to it) and then a list of property keys is obtained by calling the object's `[[OwnPropertyKeys]]` internal method. That list is then used to mark properties as non-configurable (and non-writable in the case of `Object.freeze`). 80 | 81 | Since `[[OwnPropertyKeys]]` is not allowed to return private symbols, `freeze` and `seal` cannot modify any property definitions that are keyed with private symbols. 82 | 83 | The fundamental idea is that only the code that has access to the private symbol is allowed to make changes to properties keyed by that symbol. 84 | 85 | This requires us to slightly modify the definition of a "frozen" object: An object is "frozen" if it is non-configurable and all of it's own *non-private* properties are non-configurable and non-writable. 86 | 87 | ### __*How does this work with Proxies?*__ 88 | 89 | Proxies are not able to intercept private symbols, and proxy handlers are not allowed to return any private symbols from the `ownKeys` trap. 90 | 91 | For all of the proxy internal methods that accept a property key, if that property key is a private symbol, then the proxy handler is not consulted and the operation is forwarded directly to the target object, as if there were no handler defined for that trap. 92 | 93 | This means that simple wrapping proxies "just work" with objects that use private symbols. 94 | 95 | ```js 96 | const data = Symbol.private(); 97 | 98 | class C { 99 | constructor() { 100 | this[data] = 42; 101 | } 102 | 103 | getData() { 104 | return this[data]; 105 | } 106 | } 107 | 108 | const c = new C(); 109 | const p = new Proxy(c, {}); 110 | 111 | c.getData(); // 42 112 | p.getData(); // 42 113 | ``` 114 | 115 | ### __*How does this work with membranes?*__ 116 | 117 | A membrane is a boundary between object graphs such that all access to objects "across" the boundary are required to pass through the membrane's intercession mechanism. When an object "crosses" the boundary, it is wrapped in a proxy, and any objects reachable from that proxy are also wrapped in proxies. 118 | 119 | 120 | 121 | Any membrane that allows its proxies to return *stability guarantees* must use a "shadow target" to allow the Proxy API to uphold *object model invariants*. For instance, if a proxy reveals that it is non-extensible, then it must always report that it is non-extensible, and it may not in the future allow new properties to be added. To uphold these invariants, the Proxy API verifies that values returned from proxy handlers are consistent with the shape of the proxy's target. The "shadow target" acts as a record of the object model invariants that the proxy has committed itself to. 122 | 123 | When a private symbol is used on a membrane proxy, the proxy is bypassed and the operation is applied directly to the shadow target. As it turns out, the shadow target is an ideal store for private symbol-keyed properties: it is isolated from both "wet" and "dry" object graphs and access to it is revoked when the membrane is revoked. As long as the membrane uses the shadow target technique, private symbols cannot be used to bypass the membrane. 124 | 125 | With the introduction of private symbols, the shadow target technique is transformed from a practical constraint into a necessary constraint for the implementation of secure membranes. 126 | 127 | Membranes are not transparent with respect to private symbols. Given some private symbol `p` shared by objects on both sides of the membrane, property access using `p` as a key will, in general, yield different results when applied to a membrane-proxy versus the object that it wraps. Properties that are keyed with private symbols are effectively isolated to the object graph in which they are used. 128 | 129 | It is not possible for a membrane to support both both privacy and transparency at the same time. If a membrane allows privacy, then it must reject transparency: operations involving private state must be isolated to their own object graph. If a membrane is perfectly transparent, then it cannot support privacy: operations involving supposedly "private" keys would have to be intercepted by, and leaked to, the proxy. 130 | 131 | ### __*Can private symbols be used for branding?*__ 132 | 133 | The purpose of a branding mechanism is to mark objects such that, when presented with an arbitrary object, the code that created the "brand" can determine whether or not the object has been marked. In a typical scenario, objects are branded by constructor functions so that method invocations can check whether the `this` value is an object that was actually created by the constructor function. 134 | 135 | Simply testing for the presence of a private symbol-keyed property is not sufficient for branding because a proxy can be used to transparently wrap an object. If the proxy target has a private symbol-keyed property, then the wrapping proxy will happily report that the proxy does as well: 136 | 137 | ```js 138 | const sym = Symbol.private(); 139 | const obj = { [sym]: true }; 140 | const proxy = new Proxy(obj, {}); 141 | 142 | Boolean(Reflect.getOwnPropertyDescriptor(obj, sym)); // true 143 | Boolean(Reflect.getOwnPropertyDescriptor(proxy, sym)); // true 144 | ``` 145 | 146 | Private symbols can be used for branding if we store the object reference as the property value: 147 | 148 | ```js 149 | const sym = Symbol.private(); 150 | 151 | function brand(x) { x[sym] = x } 152 | function isBranded(x) { return x[sym] === x } 153 | 154 | const obj = {}; 155 | brand(obj); 156 | 157 | const proxy = new Proxy(obj, {}); 158 | 159 | isBranded(obj); // true 160 | isBranded(proxy); // false 161 | ``` 162 | 163 | Alternatively, a `WeakSet` can be used: 164 | 165 | ```js 166 | const brand = new WeakSet(); 167 | const obj = {}; 168 | const proxy = new Proxy(obj, {}); 169 | 170 | brand.add(obj); 171 | 172 | brand.has(obj); // true 173 | brand.has(proxy); // false 174 | ``` 175 | 176 | ### __*Does the lack of automatic brand checking make code less secure?*__ 177 | 178 | Let's turn the question around: would automatic brand checking on access to private state make code *more* secure? Although automatic brand checking would help guard against malicious actors in a few simple scenarios, it is not sufficient for writing secure code. At a minimum, secure code must: 179 | 180 | - Execute in a trusted and frozen global environment. 181 | - Implement type guards on *all* inputs that are assumed to be trusted types. 182 | - Implement type guards that are executed before *any* other code is run. 183 | 184 | Automatic brand checking on access to private state seems like it helps make code more secure, but in reality leaves wide open cracks. It is better not to give the user false hope. 185 | 186 | ### __*Doesn't this proposal sacrifice "static shape" guarantees for private state?*__ 187 | 188 | In general, it is not possible to provide static shape guarantees in JavaScript, nor is that a core language strength. It is the job of the JavaScript engine to infer static shape from the structure and behavior of the program at runtime. 189 | 190 | ### __*Square brackets are ugly! Why doesn't this proposal include a more pleasant syntax?*__ 191 | 192 | This proposal adds a missing capability to the language. A future proposal may provide syntactic sugar for symbol-keyed property definition and access. Hopefully such a syntactic feature would provide sugar for both regular *and* private symbol usage. 193 | 194 | Another option is to use source-to-source transformation to convert property names with leading underscores into private symbol lookups. See [Babel Plugins](./babel-plugins) for an example. 195 | 196 | Furthermore, compile-to-JavaScript languages that support static type systems can use private symbols when generating code that accesses members declared with a `private` modifier. 197 | 198 | ### __*Does this replace private class fields and methods?*__ 199 | 200 | Yes. See [Private Symbols, or Private Fields](./symbols-or-fields.md). 201 | 202 | ## Semantics 203 | 204 | - Symbols have an additional internal value `[[Private]]`, which is either **true** or **false**. By definition, private symbols are symbols whose `[[Private]]` value is **true**. 205 | - The invariants of `[[OwnPropertyKeys]]` are modified such that it may not return any private symbols. 206 | - The definition of `[[OwnPropertyKeys]]` for ordinary objects (and exotic objects other than proxy) is modified such that private symbols are filtered from the resulting array. 207 | - The proxy definition of `[[OwnPropertyKeys]]` is modified such that an error is thrown if the handler returns any private symbols. 208 | - The proxy definitions of all internal methods that accept property keys are modified such that if the provided key is a private symbol, then the operation is forwarded to the target without consulting the proxy handler. 209 | - A new function is added for creating private symbols: `Symbol.private([description])`. 210 | - A new getter named `private` is added to `Symbol.prototype`, which returns the symbol's [[Private]] value. 211 | 212 | See [Specification Changes](spec-changes.md) for more details. 213 | -------------------------------------------------------------------------------- /assets/membrane-shadow-target.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 39 | Page-1 40 | 42 | 43 | 44 | Rectangle 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | Can 55 | Shadow Target 56 | 57 | Sheet.20 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | Shadow Target 71 | 72 | 73 | Circle 74 | Real target 75 | 76 | 77 | 78 | 79 | 80 | 81 | Real target 83 | 84 | Hexagon 85 | Proxy 86 | 87 | 88 | 89 | 90 | 91 | 92 | Proxy 93 | 94 | Dynamic connector 95 | 96 | 97 | 98 | Dynamic connector.24 99 | 101 | 102 | 103 | Sheet.25 104 | Membrane 105 | 106 | 107 | 108 | Membrane 109 | 110 | Circle.27 111 | Real target 112 | 113 | 114 | 115 | 116 | 117 | 118 | Real target 120 | 121 | Dynamic connector.28 122 | o.x 123 | 124 | 125 | 126 | 127 | o.x 128 | 129 | 130 | 131 | 132 | Can.29 133 | Shadow Target 134 | 135 | Sheet.30 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | Shadow Target 149 | 150 | 151 | Hexagon.31 152 | Proxy 153 | 154 | 155 | 156 | 157 | 158 | 159 | Proxy 160 | 161 | Dynamic connector.32 162 | 163 | 164 | 165 | Dynamic connector.34 166 | p.x 167 | 168 | 169 | 170 | 171 | p.x 172 | 173 | Dynamic connector.35 174 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /babel-plugins/README.md: -------------------------------------------------------------------------------- 1 | # Babel Plugins 2 | 3 | ## underscore-to-private 4 | 5 | Converts underscore property names into computed property names backed by a private symbol. 6 | 7 | The following code: 8 | 9 | ```js 10 | 11 | class Point { 12 | 13 | constructor(x, y) { 14 | this._x = x; 15 | this._y = y; 16 | } 17 | 18 | toString() { 19 | return `<${ this._x }:${ this._y }>`; 20 | } 21 | 22 | add(other) { 23 | return new Point(this._x + other._x, this._y + other._y); 24 | } 25 | 26 | } 27 | 28 | ``` 29 | 30 | is transformed into: 31 | 32 | ```js 33 | var _x = Symbol.private('x'), 34 | _y = Symbol.private('y'); 35 | 36 | class Point { 37 | 38 | constructor(x, y) { 39 | this[_x] = x; 40 | this[_y] = y; 41 | } 42 | 43 | toString() { 44 | return `<${ this[_x] }:${ this[_y] }>` 45 | } 46 | 47 | add(other) { 48 | return new Point(this[_x] + other[_x], this[_y] + other[_y]); 49 | } 50 | 51 | } 52 | 53 | ``` 54 | -------------------------------------------------------------------------------- /babel-plugins/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "@babel/code-frame": { 6 | "version": "7.0.0-rc.1", 7 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-rc.1.tgz", 8 | "integrity": "sha512-qhQo3GqwqMUv03SxxjcEkWtlkEDvFYrBKbJUn4Dtd9amC2cLkJ3me4iYUVSBbVXWbfbVRalEeVBHzX4aQYKnBg==", 9 | "dev": true, 10 | "requires": { 11 | "@babel/highlight": "7.0.0-rc.1" 12 | } 13 | }, 14 | "@babel/core": { 15 | "version": "7.0.0-rc.1", 16 | "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.0.0-rc.1.tgz", 17 | "integrity": "sha512-CvuSsq+LFs9N4SJG8MnNPI0hnl913HK1OqG3NEfejOKo+JqtVuxpmAFyXIDogX2x668xqFKAW6EQiCIcUHklMg==", 18 | "dev": true, 19 | "requires": { 20 | "@babel/code-frame": "7.0.0-rc.1", 21 | "@babel/generator": "7.0.0-rc.1", 22 | "@babel/helpers": "7.0.0-rc.1", 23 | "@babel/parser": "7.0.0-rc.1", 24 | "@babel/template": "7.0.0-rc.1", 25 | "@babel/traverse": "7.0.0-rc.1", 26 | "@babel/types": "7.0.0-rc.1", 27 | "convert-source-map": "^1.1.0", 28 | "debug": "^3.1.0", 29 | "json5": "^0.5.0", 30 | "lodash": "^4.17.10", 31 | "resolve": "^1.3.2", 32 | "semver": "^5.4.1", 33 | "source-map": "^0.5.0" 34 | } 35 | }, 36 | "@babel/generator": { 37 | "version": "7.0.0-rc.1", 38 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-rc.1.tgz", 39 | "integrity": "sha512-Ak4n780/coo+L9GZUS7V/IGJilP11t4UoWl0J9cG3jso4KkDGQcqdx4Y6gJAiXng+sDfvzUmvWfM1hZwH82J0A==", 40 | "dev": true, 41 | "requires": { 42 | "@babel/types": "7.0.0-rc.1", 43 | "jsesc": "^2.5.1", 44 | "lodash": "^4.17.10", 45 | "source-map": "^0.5.0", 46 | "trim-right": "^1.0.1" 47 | } 48 | }, 49 | "@babel/helper-function-name": { 50 | "version": "7.0.0-rc.1", 51 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-rc.1.tgz", 52 | "integrity": "sha512-fDbWxdYYbFNzcI5jn3qsPxHI1UCXwvFk0kGytGce/FEBYEPXBqycKknC8Oqiub8DzGtmTcvnqcm/cl/qxzeuiQ==", 53 | "dev": true, 54 | "requires": { 55 | "@babel/helper-get-function-arity": "7.0.0-rc.1", 56 | "@babel/template": "7.0.0-rc.1", 57 | "@babel/types": "7.0.0-rc.1" 58 | } 59 | }, 60 | "@babel/helper-get-function-arity": { 61 | "version": "7.0.0-rc.1", 62 | "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-rc.1.tgz", 63 | "integrity": "sha512-5+ydaIRxT42FSDqvoXIDksCGlW1903xC73HQnQCFF1YuV7VcIf+9M4+tRZulLlYlshw7ILA+4SiYsKoDlC0Irg==", 64 | "dev": true, 65 | "requires": { 66 | "@babel/types": "7.0.0-rc.1" 67 | } 68 | }, 69 | "@babel/helper-plugin-utils": { 70 | "version": "7.0.0-rc.1", 71 | "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0-rc.1.tgz", 72 | "integrity": "sha512-8ZNzqHXDhT/JjnBvrLKu8AL7NhONVIsnrfyQNm3PJNmufIER5kcIa3OxPMGWgNqox2R8WeQ6YYzYTLNXqq4kgQ==", 73 | "dev": true 74 | }, 75 | "@babel/helper-split-export-declaration": { 76 | "version": "7.0.0-rc.1", 77 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-rc.1.tgz", 78 | "integrity": "sha512-hz6QmlnaBFYt4ra8DfRLCMgrI7yfwQ13kJtufSO5dVCasxmAng2LeeQiT6H4iN5TpFONcayp5f/2mXqHH/zn/g==", 79 | "dev": true, 80 | "requires": { 81 | "@babel/types": "7.0.0-rc.1" 82 | } 83 | }, 84 | "@babel/helpers": { 85 | "version": "7.0.0-rc.1", 86 | "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.0.0-rc.1.tgz", 87 | "integrity": "sha512-4+AkDbZ0Usr7mNH4wGX8fVx4WJzHdrcjRkJy52EIWyBAQEoKqb5HXca1VjejWtnVwaGwW7zk/h6oQ9FQPywQfA==", 88 | "dev": true, 89 | "requires": { 90 | "@babel/template": "7.0.0-rc.1", 91 | "@babel/traverse": "7.0.0-rc.1", 92 | "@babel/types": "7.0.0-rc.1" 93 | } 94 | }, 95 | "@babel/highlight": { 96 | "version": "7.0.0-rc.1", 97 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-rc.1.tgz", 98 | "integrity": "sha512-5PgPDV6F5s69XNznTcP0za3qH7qgBkr9DVQTXfZtpF+3iEyuIZB1Mjxu52F5CFxgzQUQJoBYHVxtH4Itdb5MgA==", 99 | "dev": true, 100 | "requires": { 101 | "chalk": "^2.0.0", 102 | "esutils": "^2.0.2", 103 | "js-tokens": "^3.0.0" 104 | } 105 | }, 106 | "@babel/parser": { 107 | "version": "7.0.0-rc.1", 108 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-rc.1.tgz", 109 | "integrity": "sha512-rC+bIz2eZnJlacERmJO25UAbXVZttcSxh0Px0gRGinOTzug5tL7+L9urfIdSWlv1ZzP03+f2xkOFLOxZqSsVmQ==", 110 | "dev": true 111 | }, 112 | "@babel/plugin-syntax-class-properties": { 113 | "version": "7.0.0-rc.1", 114 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.0.0-rc.1.tgz", 115 | "integrity": "sha512-mWUD9BevSNhbsgwLgHZmd89keY4lgCoSbOeDo3ZiyyMc5y4fjSm+2LTHi/GeRyO6AnBbqTbPmFlznPdq15k7/g==", 116 | "dev": true, 117 | "requires": { 118 | "@babel/helper-plugin-utils": "7.0.0-rc.1" 119 | } 120 | }, 121 | "@babel/template": { 122 | "version": "7.0.0-rc.1", 123 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-rc.1.tgz", 124 | "integrity": "sha512-gPLng2iedNlkaGD0UdwaUByQXK8k4bnaoq2RH5JgR2mqHvh2RyjkDdaMbZFlSss1Iu8+PrXwbIRworTl8iRqbA==", 125 | "dev": true, 126 | "requires": { 127 | "@babel/code-frame": "7.0.0-rc.1", 128 | "@babel/parser": "7.0.0-rc.1", 129 | "@babel/types": "7.0.0-rc.1", 130 | "lodash": "^4.17.10" 131 | } 132 | }, 133 | "@babel/traverse": { 134 | "version": "7.0.0-rc.1", 135 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-rc.1.tgz", 136 | "integrity": "sha512-lNOpJ5xzakg+fCobQQHdeDRYeN54b+bAZpeTYMeeYPAvN+hTldg9/FSNKYEMRs5EWoQ0Yt74gwq98InSORdSDQ==", 137 | "dev": true, 138 | "requires": { 139 | "@babel/code-frame": "7.0.0-rc.1", 140 | "@babel/generator": "7.0.0-rc.1", 141 | "@babel/helper-function-name": "7.0.0-rc.1", 142 | "@babel/helper-split-export-declaration": "7.0.0-rc.1", 143 | "@babel/parser": "7.0.0-rc.1", 144 | "@babel/types": "7.0.0-rc.1", 145 | "debug": "^3.1.0", 146 | "globals": "^11.1.0", 147 | "lodash": "^4.17.10" 148 | } 149 | }, 150 | "@babel/types": { 151 | "version": "7.0.0-rc.1", 152 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-rc.1.tgz", 153 | "integrity": "sha512-MBwO1JQKin9BwKTGydrYe4VDJbStCUy35IhJzeZt3FByOdx/q3CYaqMRrH70qVD2RA7+Xk8e3RN0mzKZkYBYuQ==", 154 | "dev": true, 155 | "requires": { 156 | "esutils": "^2.0.2", 157 | "lodash": "^4.17.10", 158 | "to-fast-properties": "^2.0.0" 159 | } 160 | }, 161 | "ansi-styles": { 162 | "version": "3.2.1", 163 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 164 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 165 | "dev": true, 166 | "requires": { 167 | "color-convert": "^1.9.0" 168 | } 169 | }, 170 | "chalk": { 171 | "version": "2.4.1", 172 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", 173 | "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", 174 | "dev": true, 175 | "requires": { 176 | "ansi-styles": "^3.2.1", 177 | "escape-string-regexp": "^1.0.5", 178 | "supports-color": "^5.3.0" 179 | } 180 | }, 181 | "color-convert": { 182 | "version": "1.9.2", 183 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", 184 | "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", 185 | "dev": true, 186 | "requires": { 187 | "color-name": "1.1.1" 188 | } 189 | }, 190 | "color-name": { 191 | "version": "1.1.1", 192 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", 193 | "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", 194 | "dev": true 195 | }, 196 | "colors": { 197 | "version": "1.3.1", 198 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.1.tgz", 199 | "integrity": "sha512-jg/vxRmv430jixZrC+La5kMbUWqIg32/JsYNZb94+JEmzceYbWKTsv1OuTp+7EaqiaWRR2tPcykibwCRgclIsw==", 200 | "dev": true 201 | }, 202 | "convert-source-map": { 203 | "version": "1.5.1", 204 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", 205 | "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", 206 | "dev": true 207 | }, 208 | "debug": { 209 | "version": "3.1.0", 210 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 211 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 212 | "dev": true, 213 | "requires": { 214 | "ms": "2.0.0" 215 | } 216 | }, 217 | "diff": { 218 | "version": "3.5.0", 219 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 220 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 221 | "dev": true 222 | }, 223 | "escape-string-regexp": { 224 | "version": "1.0.5", 225 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 226 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 227 | "dev": true 228 | }, 229 | "esutils": { 230 | "version": "2.0.2", 231 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 232 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 233 | "dev": true 234 | }, 235 | "globals": { 236 | "version": "11.7.0", 237 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", 238 | "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", 239 | "dev": true 240 | }, 241 | "has-flag": { 242 | "version": "3.0.0", 243 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 244 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 245 | "dev": true 246 | }, 247 | "js-tokens": { 248 | "version": "3.0.2", 249 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", 250 | "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", 251 | "dev": true 252 | }, 253 | "jsesc": { 254 | "version": "2.5.1", 255 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", 256 | "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", 257 | "dev": true 258 | }, 259 | "json5": { 260 | "version": "0.5.1", 261 | "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", 262 | "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", 263 | "dev": true 264 | }, 265 | "lodash": { 266 | "version": "4.17.10", 267 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", 268 | "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", 269 | "dev": true 270 | }, 271 | "ms": { 272 | "version": "2.0.0", 273 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 274 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 275 | "dev": true 276 | }, 277 | "path-parse": { 278 | "version": "1.0.6", 279 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 280 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 281 | "dev": true 282 | }, 283 | "resolve": { 284 | "version": "1.8.1", 285 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", 286 | "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", 287 | "dev": true, 288 | "requires": { 289 | "path-parse": "^1.0.5" 290 | } 291 | }, 292 | "semver": { 293 | "version": "5.5.1", 294 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", 295 | "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", 296 | "dev": true 297 | }, 298 | "source-map": { 299 | "version": "0.5.7", 300 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 301 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", 302 | "dev": true 303 | }, 304 | "supports-color": { 305 | "version": "5.5.0", 306 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 307 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 308 | "dev": true, 309 | "requires": { 310 | "has-flag": "^3.0.0" 311 | } 312 | }, 313 | "to-fast-properties": { 314 | "version": "2.0.0", 315 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 316 | "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", 317 | "dev": true 318 | }, 319 | "trim-right": { 320 | "version": "1.0.1", 321 | "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", 322 | "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", 323 | "dev": true 324 | } 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /babel-plugins/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@babel/core": "^7.0.0-rc.1", 4 | "@babel/plugin-syntax-class-properties": "^7.0.0-rc.1", 5 | "colors": "^1.3.1", 6 | "diff": "^3.5.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /babel-plugins/src/underscore-to-private.js: -------------------------------------------------------------------------------- 1 | const syntaxClassProps = require('@babel/plugin-syntax-class-properties').default; 2 | 3 | /* 4 | 5 | This plugin converts all underscore-prefixed property names into computed 6 | property names where the property name expression evaluates to a generated 7 | private symbol. 8 | 9 | */ 10 | 11 | 12 | function underscoreToPrivatePlugin({ types: t }) { 13 | 14 | function getSymbolIdentifier(path, names) { 15 | let name = ''; 16 | switch (path.node.type) { 17 | case 'Identifier': name = path.node.name; break; 18 | case 'StringLiteral': name = path.node.value; break; 19 | } 20 | 21 | if (!name.startsWith('_')) { 22 | return null; 23 | } 24 | 25 | let symbolName = names.get(name); 26 | if (symbolName) { 27 | return t.identifier(symbolName); 28 | } 29 | 30 | let { scope } = path; 31 | while (scope.parent) { 32 | scope = scope.parent; 33 | } 34 | 35 | let ident = scope.generateUidIdentifier(name); 36 | names.set(name, ident.name); 37 | 38 | scope.push({ 39 | id: t.identifier(ident.name), 40 | init: t.callExpression( 41 | t.memberExpression( 42 | t.identifier('Symbol'), 43 | t.identifier('private') 44 | ), 45 | [t.stringLiteral(name.slice(1))], 46 | ), 47 | }); 48 | 49 | return ident; 50 | } 51 | 52 | function visitObjectMember(path, names) { 53 | let name = getSymbolIdentifier(path.get('key'), names); 54 | if (name) { 55 | path.node.computed = true; 56 | path.node.key = name; 57 | } 58 | } 59 | 60 | return { 61 | 62 | inherits: syntaxClassProps, 63 | 64 | pre() { 65 | this.names = new Map(); 66 | }, 67 | 68 | visitor: { 69 | 70 | MemberExpression(path) { 71 | let name = getSymbolIdentifier(path.get('property'), this.names); 72 | if (name) { 73 | path.node.computed = true; 74 | path.node.property = name; 75 | } 76 | }, 77 | 78 | BinaryExpression(path) { 79 | let { node } = path; 80 | if (node.operator === 'in' && node.left.type === 'StringLiteral') { 81 | let name = getSymbolIdentifier(path.get('left'), this.names); 82 | if (name) { 83 | node.left = name; 84 | } 85 | } 86 | }, 87 | 88 | ObjectMember(path) { 89 | visitObjectMember(path, this.names); 90 | }, 91 | 92 | ObjectMethod(path) { 93 | visitObjectMember(path, this.names); 94 | }, 95 | 96 | ClassMethod(path) { 97 | visitObjectMember(path, this.names); 98 | }, 99 | 100 | ClassProperty(path) { 101 | visitObjectMember(path, this.names); 102 | }, 103 | 104 | }, 105 | }; 106 | } 107 | 108 | module.exports = underscoreToPrivatePlugin; 109 | -------------------------------------------------------------------------------- /babel-plugins/test/underscore-to-private.js: -------------------------------------------------------------------------------- 1 | const babel = require('@babel/core'); 2 | const diff = require('diff'); 3 | const colors = require('colors'); 4 | const assert = require('assert'); 5 | 6 | function test(literals) { 7 | let [input, output] = literals[0].split(/\/\/=+/).map(s => s.trim()); 8 | 9 | let { code } = babel.transform(input, { 10 | plugins: ['./src/underscore-to-private.js'], 11 | }); 12 | 13 | let result = diff.diffChars(code, output); 14 | if (!result.some(part => (part.added || part.removed) && part.value.trim())) { 15 | return; 16 | } 17 | 18 | console.log( 19 | result.map(part => { 20 | let color = part.added ? 'green' : part.removed ? 'red' : 'grey'; 21 | return part.value[color]; 22 | }).join('') 23 | ); 24 | 25 | assert.ok(false); 26 | } 27 | 28 | test` 29 | 30 | class C { 31 | _foo = 1; 32 | 33 | constructor() { 34 | this._foo = 2; 35 | } 36 | 37 | _m() { 38 | ('_foo' in this); 39 | } 40 | } 41 | 42 | //============= 43 | 44 | var _foo = Symbol.private("foo"), 45 | _m = Symbol.private("m"); 46 | 47 | class C { 48 | [_foo] = 1; 49 | 50 | constructor() { 51 | this[_foo] = 2; 52 | } 53 | 54 | [_m]() { 55 | _foo in this; 56 | } 57 | 58 | } 59 | 60 | `; 61 | 62 | test` 63 | 64 | let obj = { 65 | _x: 1, 66 | _y: 2, 67 | _z() { this['_x'] }, 68 | get _a() {}, 69 | '_b': 3, 70 | } 71 | 72 | //================ 73 | 74 | var _x = Symbol.private("x"), 75 | _y = Symbol.private("y"), 76 | _z = Symbol.private("z"), 77 | _a = Symbol.private("a"), 78 | _b = Symbol.private("b"); 79 | 80 | let obj = { 81 | [_x]: 1, 82 | [_y]: 2, 83 | 84 | [_z]() { 85 | this[_x]; 86 | }, 87 | 88 | get [_a]() {}, 89 | 90 | [_b]: 3 91 | 92 | }; 93 | 94 | ` 95 | 96 | test` 97 | 98 | function F(_x) { 99 | this._x = _x; 100 | } 101 | 102 | //================== 103 | 104 | var _x2 = Symbol.private("x"); 105 | 106 | function F(_x) { 107 | this[_x2] = _x; 108 | } 109 | 110 | ` 111 | -------------------------------------------------------------------------------- /membranes.md: -------------------------------------------------------------------------------- 1 | # Private Symbols and Membranes 2 | 3 | ## Definitions 4 | 5 | A **secure membrane** is a boundary between object graphs `A` and `B` such that: 6 | 7 | - No objects in `A` hold a direct reference to an object in `B` 8 | - No objects in `B` hold a direct reference to an object in `A` 9 | - All data flow between `A` and `B` is mediated by code associated with the membrane 10 | 11 | A **shadow target** is a proxy target, distinct from the wrapped object, used by a membrane-proxy to record object model stability claims for the purpose of invariant enforcement. The **shadow target** is only accessible from its associated proxy. 12 | 13 | ## Claims 14 | 15 | ### C1. All objects reachable from the global scope of both `A` and `B` are frozen. 16 | 17 | Assume that an object reachable from the global scope of both graphs is not frozen. Then code in `A` can store a reference to an object in `A` by adding a property to an object reachable from the global scope. Code in `B` can then read that property and obtain a direct reference to an object in `A`. 18 | 19 | ### C2. No objects in `A` or `B` hold a direct reference to a membrane-proxy wrapping an object in its own graph. 20 | 21 | Assume that an object in `A` has a reference to a membrane-proxy that wraps an object in `A`. 22 | 23 | - Let `P_a1` be the membrane-proxy that wraps `a1`. 24 | - Let `a2` be a different object in `A`. 25 | - Code in `A` performs `P_a1.[[Set]](a2, "$")`. 26 | - `P_a1` thinks that `a2` is an object in `B` and wraps it in a proxy `P_a2` before sending it to `a1`. 27 | - Let `P_b1` be the membrane-proxy for an object `b1` in `B`. 28 | - Responding to the set operation, `a1` performs `P_b1.[[Set]](P_a2, "$")`, 29 | - The proxy `P_b1` unwraps `P_a2` and sends `a2` to `b1`. 30 | - `b1` now has a direct reference to `a2`. 31 | 32 | ### C4. No object in `A` can read or write to a private-symbol-named property of a shadow target for an object in `A` 33 | 34 | Since the shadow target is not accessible outside of its associated membrane-proxy, the only way to read or write private-symbol named properties on a shadow target is through a direct reference to the membrane-proxy. But by **C2** no object in `A` holds a direct reference to a membrane-proxy wrapping an object in `A`. 35 | 36 | ### C5. A secure membrane whose proxies use shadow targets remains secure when a private symbol is passed between `A` and `B`. 37 | 38 | Assume that a private symbol `pSym` is passed from `A` to `B` and that an object in `B` has obtained a direct reference to an object in `A`. Then `pSym` must have been used to obtain the reference by application to one of the following: 39 | 40 | 1. A shared global reference 41 | 1. An object in `B` 42 | 1. A membrane-proxy for an object in `A` 43 | 44 | If `pSym` was used against a shared global reference, then the global reference was not frozen prior to code from `A` executing. By **C1**, this not possible for a secure membrane. 45 | 46 | If `pSym` was used against an object in `B`, then an object in `B` would have already had a direct reference to an object in `A`. But this is not possible since the membrane was secure. 47 | 48 | If `pSym` was used against a membrane-proxy for an object in `A`, then the proxy's shadow target would have already had direct reference to an object in `A` stored in a private-symbol named property. Code in `A` must have placed it there. But by **C4**, this is not possible for a secure membrane. 49 | -------------------------------------------------------------------------------- /spec-changes.md: -------------------------------------------------------------------------------- 1 | # ECMA262 Specification Changes 2 | 3 | ## Changes to the Symbol Type 4 | 5 | ### 6.1.5 The Symbol Type 6 | 7 | Add the following item to the description of the Symbol type: 8 | 9 | - Each Symbol value immutably holds a value called [[Private]] that is either **true** or **false**. 10 | 11 | ### 6.1.5.1 Well-Known Symbols 12 | 13 | Add a clause specifying that well-known symbols have a [[Private]] value of **false**. 14 | 15 | ## Changes to OwnPropertyKeys 16 | 17 | ### 6.1.7.3 Invariants of the Essential Internal Methods 18 | 19 | #### [[OwnPropertyKeys]] ( ) 20 | 21 | Modify the list of invariants: 22 | 23 | - The return value must be a List. 24 | - The returned list must not contain any duplicate entries. 25 | - The Type of each element of the returned List is either String or Symbol. 26 | - The returned list must not contain any symbols whose [[Private]] value is **true**. 27 | - The returned List must contain at least the keys of all non-configurable non-private own properties that have previously been observed. 28 | - If the object is non-extensible, the returned list must contain only the keys of all non-private own properties of the object that are observable using [[GetOwnProperty]]. 29 | 30 | *The term "non-private own property" is not well-defined.* 31 | 32 | ### 9.1.11.1 OrdinaryOwnPropertyKeys ( _O_ ) 33 | 34 | Modify step 4: 35 | 36 | - For each own property key _P_ of _O_ that is a Symbol, in ascending chronological order of property creation, do 37 | - If _P_'s [[Private]] value is **false**, add _P_ as the last element of _keys_. 38 | 39 | Update identical steps for exotic object [[OwnPropertyKeys]]: 40 | 41 | - 9.4.3.3 (String) 42 | - 9.4.5.6 (Integer-Indexed) 43 | 44 | ## Changes to Proxy Internal Methods 45 | 46 | ### 9.5.5 [[GetOwnProperty]] ( _P_ ) 47 | 48 | Add after step 5: 49 | 50 | - If Type(_P_) is Symbol and _P_'s [[Private]] value is **true**, then 51 | - Let _trap_ be **undefined**. 52 | - Else, 53 | - Let _trap_ be ? GetMethod(_handler_, **"getOwnPropertyDescriptor"**). 54 | 55 | Update similar steps for the following internal methods: 56 | 57 | - 9.5.6 [[DefineOwnProperty]] ( P, Desc ) 58 | - 9.5.7 [[HasProperty]] ( P ) 59 | - 9.5.8 [[Get]] ( P, Receiver ) 60 | - 9.5.9 [[Set]] ( P, V, Receiver ) 61 | - 9.5.10 [[Delete]] ( P ) 62 | 63 | ### 9.5.11 [[OwnPropertyKeys]] ( ) 64 | 65 | Add after step 9: 66 | 67 | - If _trapResult_ contains any Symbol values whose [[Private]] value is **true**, throw a **TypeError** exception. 68 | 69 | Add the following item to the note: 70 | 71 | - The returned List contains no Symbol values whose [[Private]] value is **true**. 72 | 73 | Modify note regarding invariants: 74 | 75 | - The result of [[OwnPropertyKeys]] is a List. 76 | - The returned List contains no duplicate entries. 77 | - The Type of each result List element is either String or Symbol. 78 | - The returned list must not contain any symbols whose [[Private]] value is **true**. 79 | - The result List must contain the keys of all non-configurable non-private own properties of the target object. 80 | - If the target object is not extensible, then the result List must contain all the keys of the non-private own properties of the target object and no other values. 81 | 82 | ## Changes to the Symbol API 83 | 84 | ### 19.4.1.1 Symbol ( [ _description_ ] ) 85 | 86 | Modify step 4: 87 | 88 | - Return a new unique Symbol value whose [[Description]] value is _descString_ and whose [[Private]] value is **false**. 89 | 90 | ### 19.4.2 Properties of the Symbol Constructor 91 | 92 | #### Symbol.private ( _description_ ) 93 | 94 | - If NewTarget is not **undefined**, throw a **TypeError** exception. 95 | - If _description_ is **undefined**, let _descString_ be **undefined**. 96 | - Else, let _descString_ be ? ToString(_description_). 97 | - Return a new unique Symbol value whose [[Description]] value is _descString_ and whose [[Private]] value is **true**. 98 | 99 | ### 19.4.3 Properties of the Symbol Prototype Object 100 | 101 | #### get Symbol.prototype.private 102 | 103 | - Let _sym_ be ? thisSymbolValue(**this** value). 104 | - Let _isPrivate_ be _sym_'s [[Private]] value. 105 | - Assert: Type(_isPrivate_) is Boolean. 106 | - Return _isPrivate_. 107 | -------------------------------------------------------------------------------- /symbols-or-fields.md: -------------------------------------------------------------------------------- 1 | # Private Symbols, or Private Fields? 2 | 3 | Private symbols were originally dropped from the ES6 specification because of the complexity that they introduced into the object model, particularly with respect to proxies. At the time, various mechanisms were proposed to allow some level of interaction between proxies and private symbols, but each of these mechanisms felt inelegant and unjustified. It seemed that by using WeakMap as the model for private state we could avoid those issues. 4 | 5 | It turns out that we weren't able to eliminate object model complexity by pursuing the WeakMap model of private state. In fact, we've traded local complexity for global complexity. We've smoothed out a winkle in one corner of the language, only to find that we've pushed wrinkles into every other corner. 6 | 7 | A private state model based on private symbols has the following advantages over private fields based on WeakMaps: 8 | 9 | ## A more general private state model 10 | 11 | Unlike private fields, private symbols are not restricted to classes. In general, the language will be stronger if we do not create synthetic distinctions between objects created via class constructors and objects created any other way. 12 | 13 | Can private fields be extended to object literals in the future? Given that fields are lexically scoped, it is difficult to see how objects created via literals can share access to private state without further complication of the private field model. 14 | 15 | ## Better support for cooperative objects (friends) 16 | 17 | When using private state in JavaScript, friendship patterns within a single module are quite common. With private fields, the only way to support this use case is by introducing decorators. As such, decorators and private fields are tightly bound. 18 | 19 | With private symbols, friendship is trivial: we simply use the same private symbol variable for any objects within the friendship relation. 20 | 21 | ## Better interaction with decorators 22 | 23 | Decorators and private fields are tightly bound: the decorator proposal must reify the private field as a novel map-like object, and private fields need decorators in order to satisfy the core use case of "friendship". Furthermore, decorator functions must be prepared to accept a "key" parameter that is not actually a valid property key. 24 | 25 | Private symbols, on the other hand, simply flow through decorators just like any other symbol key. There are no special rules that must be applied to support private state. 26 | 27 | ## A simpler private state object model 28 | 29 | With private fields, users have to learn a new model of object state. They have to understand that for some kinds of member names prototype inheritance is applied, and for others it is not. They have to understand that for some kinds of names, proxies don't work, and for others they do. They have to understand that some names can throw a TypeError and others do not. 30 | 31 | Private symbols do not require that the user learn anything new about the JavaScript object model. There are simply properties, and some of them can be hidden from reflection. 32 | 33 | ## A simpler model for private class methods 34 | 35 | Private methods introduce an interesting question regarding the mental model of private state. Given that methods can be getters or setters, are private methods "properties" or are they "values"? Is private state more property-like or WeakMap-like? How is a user supposed to think about this? 36 | 37 | Also, why is a method named with "#" installed on the instance, whereas a method named without "#" is installed on the prototype? 38 | 39 | With private symbols there is nothing new to learn. Methods keyed with private symbols are regular properties installed on the prototype in the normal manner. 40 | 41 | ## Better interaction with static class fields 42 | 43 | Private static fields introduce a well-known hazard: accessing a private static field through `this` will throw a `TypeError` if `this` is a subclass. 44 | 45 | When accessing a property using private symbols the prototype chain is traversed and the hazard disappears. 46 | 47 | ## Better interaction with simple wrapping proxies 48 | 49 | Users have developed patterns for proxy usage which do not require a full membrane. In these scenarios, proxies are used to wrap an application-domain object and intercede (by logging, for example) when code interacts with the object. 50 | 51 | A simple wrapping proxy will fail when it attempts to wrap an object that has private fields. Because private field access is based on identity, a `TypeError` will be thrown when code attempts to access the private state of the proxy wrapper. 52 | 53 | With private symbols, access is forwarded directly to the proxy target, allowing simple wrapping proxies to function as expected. 54 | 55 | ## A better platform for generalized symbol-name syntactic sugar 56 | 57 | If we're going to have syntactic sugar for names, why not have sugar for regular symbols too? 58 | --------------------------------------------------------------------------------