-
-
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 Ivan
I 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?