Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

JavaScript How to Access Private Fields from Chained Extended Classes

5.00/5 (2 votes)
16 Jan 2023CPOL1 min read 9.6K   36  
Access private fields from Chained Extended classes
In this post, I present an interesting thing I found with JavaScript 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.

JavaScript
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:

JavaScript
// 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)

  1. Class: The string name for the extended class that you would like to access a private field.
  2. Name: The name of the private field to access.
  3. Value: The value to set the private field to.

This special method allows you to get and set private fields directly per class.

JavaScript
     $(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.

JavaScript
// 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.

JavaScript
/**
 * 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

JavaScript
// 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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)