JavaScript How to Access Private Fields from Chained Extended Classes
Access private fields from Chained Extended classes
Introduction
While working with JavaScript class inheritance, I found something Interesting I would love to share. A way to access private class fields from classes that didn't define them, only extended them, while still keeping them private to the outside world.
Bypass errors like:
Uncaught SyntaxError: Private field '#active' must be declared in an enclosing class
Chained Extended Classes
Below is an example of creating chained multi inheritance of JavaScript classes:
// Foo3 class to extend from
class foo3 {
// Private Fields
#active = false;
get active() { return this.#active; }
set active(value) { this.#active = value; }
constructor() { }
$(Class, Name, Value = undefined) {
if (Class!=='foo3') { if (super.$)
{ return super.$(Class, Name, Value); } return; }
switch (Name) {
case '#active':
if (typeof Value === 'undefined') {
return this.#active;
} else {
this.#active = Value;
}
break;
default: if (super.$)
{ return super.$(Class, Name, Value); } break;
}
}
}
// Foo2 class to extend from
class foo2 extends foo3 {
// Private Fields
#active = 'no';
get active() { return this.#active; }
set active(value) { this.#active = value; }
constructor() { super(); }
$(Class, Name, Value = undefined) {
if (Class!=='foo2') { if (super.$)
{ return super.$(Class, Name, Value); } return; }
switch (Name) {
case '#active':
if (typeof Value === 'undefined') {
return this.#active;
} else {
this.#active = Value;
}
break;
default: if (super.$)
{ return super.$(Class, Name, Value); } break;
}
}
}
// Foo class with chained extended: foo <- foo2 <- foo3
class foo extends foo2 {
constructor() { super(); }
// Prevent outside direct access to method by override.
$(Class, Name, Value = undefined) {
throw new Error(`This method is for protected use!`);
}
test() {
// Sets foo2 #active
super.$('foo2', '#active', 'yes');
// Sets foo3 #active
super.$('foo3', '#active', true);
// Gets foo2 #active
const foo2_active = super.$('foo2','#active');
// Gets foo3 #active
const foo3_active = super.$('foo3','#active');
console.log(`Print Original Values`);
console.log(`Private - foo2.#active = 'no'`);
console.log('Private - foo3.#active = false');
console.log(`Public - this.active = 'no'`);
console.log(`\nPrint Changed Fields`);
console.log(`Private - foo2.#active = '${foo2_active}'`);
console.log(`Private - foo3.#active = ${foo3_active}`);
console.log(`Public - this.active = '${this.active}'`);
// Set this active
this.active = 'off';
console.log(`\nReprint after setting: this.active = 'off'`);
console.log(`Private - foo2.#active = '${foo2_active}'`);
console.log(`Private - foo3.#active = ${foo3_active}`);
console.log(`Public - this.active = '${this.active}'`);
}
}
const f = new foo();
f.test();
Super Recursion Method for Private Access
Notice the public
method named '$
' per each class. This method takes 3 params.
$(Class, Name, Value = undefined)
Class
: Thestring
name for the extended class that you would like to access aprivate
field.Name
: Thename
of theprivate
field to access.Value
: Thevalue
to set theprivate
field to.
This special method allows you to get
and set private
fields directly per class.
$(Class, Name, Value = undefined) {
if (Class!=='foo2')
{ if (super.$) return super.$(Class, Name, Value); return; }
switch (Name) {
case '#active':
if (typeof Value === 'undefined') {
return this.#active;
} else {
this.#active = Value;
}
break;
default: if (super.$) return super.$(Class, Name, Value);
}
}
A couple of things you need to change when creating your own class access. The class name and private fields you want to directly access. You must define everything you want to use. There is no access to private
members like this['#active']
. Only access like this.#active
. So you must declare everything you want to use per string
case. You only need to add the private fields from each class.
Console Output from Above Code
Print Original Values
Private - foo2.#active = 'no'
Private - foo3.#active = false
Public - this.active = 'no'
Print Changed Fields
Private - foo2.#active = 'yes'
Private - foo3.#active = true
Public - this.active = 'yes'
Reprint after setting: this.active = 'off'
Private - foo2.#active = 'yes'
Private - foo3.#active = true
Public - this.active = 'off'
Override Extended Private Method
Protected
methods can look like below.
// Protected Function
#message = (str) => {
console.log(`foo2:message('${str}')`);
}
I also changed the super recursive method to handle private
functions and remove having to write string class name. Now, just say the name in the signature call.
New Super Recursive $ Method
Added comment to instruct you about function parameters.
/**
* For accessing private members directly.
* @param {function} Class - The class name where the field exists.
* @param {string} Member - The member name.
* @param {any} Value - The value to set the field to. optional
* @returns {any} - Returns private field if Value is undefined.
*/
$(Class, Member, Value = undefined) {
// Jumper
if (!(Object.getPrototypeOf(this) instanceof Class) && super.$) { return super.$(Class, Member, Value); }
switch (Member) {
case '#active':
if (typeof Value === 'undefined') { return this.#active; } else { this.#active = Value; }
break;
case '#message':
if (typeof Value === 'undefined') { return this.#message; } else { this.#message = Value; }
break;
default: if (super.$) { return super.$(Class, Member, Value); } break;
}
}
Example Call to $ Method
// Change field
super.$(foo3, '#active', true);
// Override method
super.$(foo2, '#message', this.#message);
Example 2 Output
foo2:message('Hello from foo2_test()')
Overriding foo2:#message() with foo:message()
foo:message('Hello from foo2_test()')
this.active = no
this.active2 = false
this.active = true
this.active2 = Yes
Uncaught Error: This method is for protected use!
at foo.$ (test2.html:100:15)
at test2.html:131:5
History
- 16th January, 2023: Override Private Function
- 14th January, 2023: Initial version