Last active
August 13, 2021 14:51
-
-
Save ifraixedes/e9311748c961f1dbb93e to your computer and use it in GitHub Desktop.
Private properties on ES6 Classes using Proxies
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! 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; | |
} | |
} |
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.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@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.