-
-
Save ifraixedes/e9311748c961f1dbb93e to your computer and use it in GitHub Desktop.
| DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE Version 2, December 2004 | |
| (http://www.wtfpl.net/about/) | |
| Copyright (C) 2015 Ivan Fraixedes (https://ivan.fraixed.es) | |
| Everyone is permitted to copy and distribute verbatim or modified | |
| copies of this license document, and changing it is allowed as long | |
| as the name is changed. | |
| DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
| TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |
| 0. You just DO WHAT THE FUCK YOU WANT TO. |
| { | |
| "name": "private-prop-es6-class-with-proxy", | |
| "version": "0.0.0", | |
| "description": "Private properties on ES6 Classes using Proxies", | |
| "main": "private-props-es6-class-with-proxy.js", | |
| "dependencies": { | |
| "harmony-reflect": "^1.1.3" | |
| }, | |
| "devDependencies": {}, | |
| "scripts": { | |
| "test": "echo \"Error: no test specified\" && exit 1" | |
| }, | |
| "author": "Ivan Fraixedes <[email protected]> (http://ivan.fraixed.es)", | |
| "license": "WTFPL" | |
| } |
| #! iojs --harmony --harmony_proxies | |
| 'use strict'; | |
| require('harmony-reflect'); | |
| // Usual class | |
| class Person { | |
| constructor(name) { | |
| this._name = name; | |
| } | |
| get name() { | |
| return this._name; | |
| } | |
| set name(name) { | |
| this._name = name; | |
| } | |
| greeting(person) { | |
| return `hi ${person.name}`; | |
| } | |
| } | |
| // Create an object from an usual class | |
| let ivanPerson = new Person('Ivan'); | |
| // Call the getter of _name | |
| console.log(`name getter from a Person instance: ${ivanPerson.name}`); | |
| // _name property is accessible because JS classes doesn't have access scope | |
| console.log(`_name property from a Person instance: ${ivanPerson._name}`); | |
| // Let's try to protect class object | |
| let ProtectedPerson = new Proxy(Person, { | |
| get(target, name) { | |
| console.log('calling getter in Person wrapped by a proxy'); | |
| if (name.startsWith('_')) { | |
| throw new Error('Accessing to a private property is not allowed'); | |
| } else { | |
| return target[name]; | |
| } | |
| } | |
| }); | |
| // Let's create an instance of the class that we wrapped with a proxy | |
| let ivanProtectedPerson = new ProtectedPerson('Ivan'); | |
| // Call the getter of _name | |
| console.log(`name getter from a protected person instance: ${ivanProtectedPerson.name}`); | |
| // _name still accessible due proxy wrapped class Person; | |
| // a class is a Function with prototype object, proxy trap calls to the | |
| // class itself hence the target is the class, not its instances | |
| console.log(`_name property from a protected person instance: ${ivanProtectedPerson._name}`); | |
| // let's protect a base class instance | |
| let ivanPersonProtected = new Proxy(ivanPerson, { | |
| get(target, name) { | |
| if (name.startsWith('_')) { | |
| throw new Error('Accessing to a private property is not allowed'); | |
| } else { | |
| return target[name]; | |
| } | |
| } | |
| }) | |
| // Call the getter of _name | |
| console.log(`name getter from a person instance which has been protected: ${ivanPersonProtected.name}`); | |
| try { | |
| // _name property call gets trapped by the proxy | |
| console.log(`_name property from a person instance which has been protected: ${ivanPersonProtected._name}`); | |
| } catch (e) { | |
| if (e.message === 'Accessing to a private property is not allowed') { | |
| console.log('Proxy did its job!!'); | |
| } else { | |
| throw e; | |
| } | |
| } | |
| // However it's painful, having to wrap in a proxy every single instance of a class | |
| // is a repeatable tiring task, so let's try to creata a class that protect its | |
| // object instances | |
| class PersonProtected { | |
| constructor(name) { | |
| this._name = name; | |
| // In es6 it also works as in es5: remember es6 class is nothing more than a Function | |
| // and `new` call the function defined by `constructor`; | |
| // in es5 works because when you call a `new` on a function the value retuned is the | |
| // value returned inside the function if it's an object otherwise returns `this`; | |
| // in es6 remains the same for backward compatibility | |
| return new Proxy(this, { | |
| get(target, name) { | |
| if (name.startsWith('_')) { | |
| throw new Error('Accessing to a private property is not allowed'); | |
| } else { | |
| return target[name]; | |
| } | |
| } | |
| }); | |
| } | |
| get name() { | |
| return this._name; | |
| } | |
| set name(name) { | |
| this._name = name; | |
| } | |
| greeting(person) { | |
| return `hi ${person.name}`; | |
| } | |
| } | |
| let ivanPersonProtectedInstance = new PersonProtected('Ivan'); | |
| // Call the getter of _name | |
| console.log(`name getter from a PersonProtected instance: ${ivanPersonProtectedInstance.name}`); | |
| try { | |
| // _name property call gets trapped by the proxy | |
| console.log(`_name property from a PersonProtected instance: ${ivanPersonProtectedInstance._name}`); | |
| } catch (e) { | |
| if (e.message === 'Accessing to a private property is not allowed') { | |
| console.log('Class which proxy `this` did its job!!'); | |
| } else { | |
| throw e; | |
| } | |
| } |
get name() and set name(name) in the final example are to safely expose the internal _name property via, well, a getter/setter function. So instead of modifying _name directly, you can use the getter/setter to add validation/filtering logic.
@ifraixedes, when greeting, a public method, is modified to use a private property, the proxy makes it throw. In theory, it shouldn’t, because the private property isn’t exposed directly. I worry this might disqualify object proxies as a solution to private properties in ES6 classes.
class PersonProtected {
// …
greeting(person) {
return `hi ${person.name}, my name is ${this._name}`; // ← Calling a private property inside a public method
}
}console.log(ivanPersonProtectedInstance.greeting('John')); // → Error: Accessing to a private property is not allowed
// Expected: hi John, my name is IvanI like this implementation but unfortuntely it will still exposed private methods that could change private properties.
If private methods are defined with a "_" prefix then they won't be able to be called by even the class itself, so this will be a huge problem. The only way to make those private methods to work is to use another prefix or no-prefix. But this will make the interface of that class kinf of overbloat. This is 2021, so now, thankfully, we have the "#" prefix to solve this problem.
Why is there a need for getters and setters, in addition to the proxy, please?