ES6 Class Private Data Management

Original author: Dr. Axel rauschmayer
  • Transfer
This article discusses 4 approaches to managing the private data of ES6 classes:

1. Storage of data in the class constructor.
2. Labeling private properties through a naming convention (for example, prefix underscore).
3. Storage of private data in WeakMaps.
4. The use of characters in the form of keys for private properties.

The first and second approaches were widely used in ES5, and the third and fourth - appeared only in ES6. Let's take a look at each one at a time.



1. Storing data in the class constructor

Our current example is the Countdown class, which calls the action function when the counter counter becomes zero. In this case, counter and action should be stored as private variables.

First, we store action and counter in the context of the constructor class. Context is an internal data structure where the JavaScript engine stores parameters and local variables that exist when a new scope is implemented (for example, by calling a function or constructor). Here is the code:

 class Countdown {
        constructor(counter, action) {
            Object.assign(this, {
                dec() {
                    if (counter < 1) return;
                    counter--;
                    if (counter === 0) {
                        action();
                    }
                }
            });
        }
    }


Using Countdown is as follows:

    > let c = new Countdown(2, () => console.log('DONE'));
    > c.dec();
    > c.dec();
    DONE


Advantages:

● Private data is completely safe.
● The names of private properties will not conflict with the names of other private properties of the parent and child classes.

Disadvantages:

● The code becomes less elegant due to the need to define all instance methods in the constructor (at least those that need access to private data).
● This is why code spends a lot of memory. If prototype methods were used, they will be distributed.

For more information on this approach, see the Private Data in the Environment of a Constructor (Crockford Privacy Pattern) section of Speaking JavaScript.

2. Labeling private properties through a naming convention

The following code stores private data in properties with prefix underscores:

  class Countdown {
        constructor(counter, action) {
            this._counter = counter;
            this._action = action;
        }
        dec() {
            if (this._counter < 1) return;
            this._counter--;
            if (this._counter === 0) {
                this._action();
            }
        }
    }


Advantages:

● The code looks beautiful.
● You can use prototype methods.

Disadvantages:

● Unsafe. This is just an instruction for client code.
● Private property names may conflict.

3. Storage of private data in WeakMaps

This method combines the advantages of the first and second approaches: security and the possibility of using prototype methods. WeakMaps _counter and _action are used to save private data:

 let _counter = new WeakMap();
    let _action = new WeakMap();
    class Countdown {
        constructor(counter, action) {
            _counter.set(this, counter);
            _action.set(this, action);
        }
        dec() {
            let counter = _counter.get(this);
            if (counter < 1) return;
            counter--;
            _counter.set(this, counter);
            if (counter === 0) {
                _action.get(this)();
            }
        }
    }


The variables _counter and _action keep objects matching their private data. Based on how WeakMaps works, objects can be removed by the garbage collector. Private data is safe as long as WeakMaps is hidden. To protect yourself, you can also save WeakMap.prototype.get and WeakMap.prototype.set into temporary variables and call them instead of dynamically calling methods. Even if malicious code replaces these methods with those that have access to private data, this will not affect our code. However, protection extends only to the code that was launched after ours - unfortunately, it is impossible to protect the one that was launched before it.

Advantages:

● Prototype methods can be used.
● Safer than a naming convention for property keys.
● Private property names cannot conflict.

Disadvantage:

● The code is not as elegant as with a naming convention.

4. Using characters as keys for private properties.

Another location of the private data store is properties with keys as characters:

    const _counter = Symbol('counter');
    const _action = Symbol('action');
    class Countdown {
        constructor(counter, action) {
            this[_counter] = counter;
            this[_action] = action;
        }
        dec() {
            if (this[_counter] < 1) return;
            this[_counter]--;
            if (this[_counter] === 0) {
                this[_action]();
            }
        }
    }


A property with a symbol key will never conflict with another property, because each symbol is unique. In addition, the characters, although not completely, are hidden from external influences:

 let c = new Countdown(2, () => console.log('DONE'));
    console.log(Object.keys(c));
        // []
    console.log(Reflect.ownKeys(c));
        // [ Symbol(counter), Symbol(action) ]


Advantages:

● Prototype methods can be used.
● Private property names cannot conflict.

Disadvantages:

• Compared to the naming convention, the code is not so elegant.
• Insecure, since all property keys of an object, including symbols, can be determined using Reflect.ownKeys ().

5. Further reading:

Speaking. JavaScript, section Keeping Data Private;
Exploring ES6 , head of Classes;
Exploring ES6 , the head of Symbols.

Also popular now: