Syntax Sugar for protected state
Could this be achieved with decorators?
@Kenta: Is your model likely to break/leak private data into foreign contexts
once you start binding this
?
On the other hand: Should you ever need to bind this
for class functions?
Right now, I'm not entirely sure whether the implementation of "protected" can
be runtime-safe.
About your nomenclature: Why name it protected
rather than private
? Am I
missing a key difference between the two here?
FWIW, have a look at this.
It could be achieved with decorators but it won't be the same.
On a side note, that Object.assign
operation doesn't do what you think it
does, there are better patterns (boilerplates, actually ...) to copy own
properties over within their descriptors, if different from enumerable data
descriptors.
As summary, beside the implementation, I think the final sugar ain't that bad.
+1 here
P.S. the amount of wm.set(a, b).get(a)
operations instead of wm.set(a, b)
returning b
is too damn high ... I know, I know, old discussion is
old (and too late anyway)
IIRC the difference between protected and private is that a private field
works within the class but not subclasses while protected means from a
subclass you can use directly protected.whatever
even if whatever
is
not defined but it's inherited from the super class.
Private is private, and not accessible from anywhere else.
At least in PHP (and I think Java too) is like that
@Thomas
Could this be achieved with decorators?
I want to get Syntax Sugar.
But I didn't have an idea using decorators, thank you!
@kdex
About your nomenclature: Why name it
protected
rather thanprivate
? Am I missing a key difference between the two here?
This is not private
proposal, because class B extends A
can call
getName()
in class Protected_A
, like below.
class B extends A {
constructor(name, lastName, age) {
super(name, lastName);
// protected this
if(new.target === B) _(this, Protected_B);
// protected property
_(this).age = age;
}
callGetAge() {
return _(this).getAge();
}
// added
getData() {
return {
name: _(this).getName(); // Protected_A
age: _(this).getAge();
}
}
}
I'm sorry that my JavaScript code in ES2015 is too vain complex to understand.
@Andrea Giammarchi
On a side note, that
Object.assign
operation doesn't do what you think it does, there are better patterns (boilerplates, actually ...) to copy own properties over within their descriptors, if different from enumerable data descriptors.
I didn't know boilerplates, thank you!
But Object.assign
enable to copy accessors by using
Object.getOwnPropertyDescriptor
according to MDN sample
code.
Of course, this approach must be bad...
Note: In case, Object.getOwnPropertyDescriptors
proposal (Stage 0) in
ES2016 isn't same as Object.getOwnPropertyDescriptor
. I misunderstood it.
P.S. the amount of wm.set(a, b).get(a) operations instead of wm.set(a, b) returning b is too damn high ... I know, I know, old discussion is old (and too late anyway)
Sorry, it's my corner-cutting. I didn't know the amount, thank you!
to be clear: no, Object.assign does not copy accessors in a meaningful way, it copies the result of the getter, not its descriptor.
That page at MDN has also many errors, including the function myAssign that copies keys but not Symbols.
Gonna fix that, best
Sorry, I misunderstood descriptor and accessor. I try it!!
I make it!
"use strict";
// utility
function createProtectedStorage() {
const wm = new WeakMap();
return (self, protectedClass) => {
const map = wm.get(self);
if(protectedClass == null) {
if(map) {
return map;
} else {
const ret = Object.create(null);
wm.set(self, ret);
return ret;
}
}
const p = new protectedClass(self);
if(map) {
for(let key of Object.getOwnPropertyNames(map)) {
const descriptor = Object.getOwnPropertyDescriptor(map, key);
Object.defineProperty(p, key, descriptor);
}
}
wm.set(self, p);
return p;
}
}
const _ = createProtectedStorage();
class Protected_A {
constructor(publicThis) {
this.publicThis = publicThis;
}
getName() {
return `${this.publicThis.name} ${this.lastName}`;
}
}
class A {
constructor(name, lastName) {
// protected this
if(new.target === A) {
_(this, Protected_A);
}
// public property
this.name = name;
// protected property
Object.defineProperty(_(this), "lastName", {
enumerable: false,
configurable: false,
writable: false,
value: lastName
});
}
callGetName() {
// call protected method
return _(this).getName();
}
}
// extends
class Protected_B extends Protected_A {
constructor(publicThis) {
super(publicThis);
}
getAge() {
return this.age;
}
}
class B extends A {
constructor(name, lastName, age) {
super(name, lastName);
// protected this
if(new.target === B) _(this, Protected_B);
// protected property
_(this).age = age;
}
callGetAge() {
return _(this).getAge();
}
// added
getData() {
return {
name: _(this).getName(), // Protected_A
age: _(this).getAge()
}
}
getLastNameDescriptor() {
return Object.getOwnPropertyDescriptor(_(this), "lastName");
}
getAgeDescriptor() {
return Object.getOwnPropertyDescriptor(_(this), "age");
}
}
// test
const b = new B("foo", "bar", 18);
// "foo bar"
console.log(b.callGetName());
// 18
console.log(b.callGetAge());
// { "name": "foo bar", "age": 18 }
console.log(b.getData());
// { "value": "bar", "writable": false, "enumerable": false, "configurable": false }
console.log(b.getLastNameDescriptor());
// { "value": 18, "writable": true, "enumerable": true, "configurable": true }
console.log(b.getAgeDescriptor());
Sorry, I must use Object.getOwnPropertySymbols
.
maybe okay.
"use strict";
// utility
function createProtectedStorage() {
const wm = new WeakMap();
return (self, protectedClass) => {
const map = wm.get(self);
if(protectedClass == null) {
if(map) {
return map;
} else {
const ret = Object.create(null);
wm.set(self, ret);
return ret;
}
}
const p = new protectedClass(self);
if(map) {
for(let key of [ ...Object.getOwnPropertyNames(map), ...Object.getOwnPropertySymbols(map) ]) {
const descriptor = Object.getOwnPropertyDescriptor(map, key);
Object.defineProperty(p, key, descriptor);
}
}
wm.set(self, p);
return p;
}
}
const _ = createProtectedStorage();
class Protected_A {
constructor(publicThis) {
this.publicThis = publicThis;
}
getName() {
return `${this.publicThis.name} ${this.lastName}`;
}
}
class A {
constructor(name, lastName) {
// protected this
if(new.target === A) {
_(this, Protected_A);
}
// public property
this.name = name;
// protected property
Object.defineProperty(_(this), "lastName", {
enumerable: false,
configurable: false,
writable: false,
value: lastName
});
}
callGetName() {
// call protected method
return _(this).getName();
}
}
// extends
class Protected_B extends Protected_A {
getAge() {
return this[Symbol.for("$$age")];
}
}
class B extends A {
constructor(name, lastName, age) {
super(name, lastName);
// protected this
if(new.target === B) _(this, Protected_B);
// protected property
_(this)[Symbol.for("$$age")] = age;
}
callGetAge() {
return _(this).getAge();
}
// added
getData() {
return {
name: _(this).getName(), // Protected_A
[Symbol.for("$$age")]: _(this).getAge()
}
}
getLastNameDescriptor() {
return Object.getOwnPropertyDescriptor(_(this), "lastName");
}
getAgeDescriptor() {
return Object.getOwnPropertyDescriptor(_(this), Symbol.for("$$age"));
}
}
// do nothing class
class Protected_C extends Protected_B {
}
class C extends B {
constructor(...args) {
super(...args);
if(new.target === C) _(this, Protected_C);
}
}
// test
const c = new C("foo", "bar", 18);
// "foo bar"
console.log(c.callGetName());
// 18
console.log(c.callGetAge());
// { "name": "foo bar", [Symbol($$age)]: 18 }
console.log(c.getData());
// { "value": "bar", "writable": false, "enumerable": false, "configurable": false }
console.log(c.getLastNameDescriptor());
// { "value": 18, "writable": true, "enumerable": true, "configurable": true }
console.log(c.getAgeDescriptor());
for(let key of [ ...Object.getOwnPropertyNames(map), ...Object.getOwnPropertySymbols(map) ])
... or ...
for(let key of Reflect.ownKeys(map))
@Andrea Giammarchi
Thank you for supporting my code, I got JavaScript knowledge a lot!
@Subscribers
If there is not any problems, would you kindly discuss about this syntax sugar?
It would be helpful to link to gists or github repos rather than pasting code inline.
You may also be interested to read this proposal: wycats/javascript
Thank you for your advice, I created a Github repository. petamoriken/es-protected-state
Does anyone help me with being TC39 champion for my personal protected state strawman?
Dear ES discuss subscribers,
I'm Kenta Moriuchi,
Department of Art and Information Design
Kyushu University in Japan
I propose
Protected State
.In ES2015:
// utility function createProtectedStorage() { const wm = new WeakMap(); return (self, protectedClass) => { const map = wm.get(self); if(protectedClass == null) { return map || wm.set(self, Object.create(null)).get(self); } const p = new protectedClass(self); if(map) { Object.assign(p, map); } return wm.set(self, p).get(self); } } const _ = createProtectedStorage(); class Protected_A { constructor(publicThis) { this.publicThis = publicThis; } getName() { return `${this.publicThis.name} ${this.lastName}`; } } class A { constructor(name, lastName) { // protected this if(new.target === A) _(this, Protected_A); // public property this.name = name; // protected property _(this).lastName = lastName; } callGetName() { // call protected method return _(this).getName(); } } // test const a = new A("foo", "bar"); // "foo bar" console.log(a.callGetName()); // "foo" console.log(a.name); // undefined console.log(a.lastName); // extends class Protected_B extends Protected_A { constructor(publicThis) { super(publicThis); } getAge() { return this.age; } } class B extends A { constructor(name, lastName, age) { super(name, lastName); // protected this if(new.target === B) _(this, Protected_B); // protected property _(this).age = age; } callGetAge() { return _(this).getAge(); } } // test const b = new B("foo", "bar", 18); // "foo bar" console.log(b.callGetName()); // 18 console.log(b.callGetAge());
As Syntax Suger (ES Next):
class A { protected lastName; constructor(name, lastName) { // public property this.name = name; // protected property protected.lastName = lastName; } protected getName() { return `${this.name} ${protected.lastName}`; } callGetName() { return protected.getName(); } } class B extends A { protected age; constructor(name, lastName, age) { super(name, lastName); protected.age = age; } protected getAge() { return protected.age; } callGetAge() { return protected.getAge(); } }
Please discuss this proposal, thank you.